summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--images/icons48/column.pngbin0 -> 1787 bytes
-rw-r--r--images/icons48/earth.pngbin0 -> 4520 bytes
-rw-r--r--images/icons48/gear.pngbin0 -> 4418 bytes
-rw-r--r--images/icons48/keys.pngbin0 -> 4295 bytes
-rw-r--r--images/icons48/mail_new.pngbin0 -> 3944 bytes
-rw-r--r--images/icons48/server.pngbin0 -> 3556 bytes
-rw-r--r--images/icons48/table.pngbin0 -> 1574 bytes
-rw-r--r--index.html12
-rw-r--r--mxClient.min.js1564
-rw-r--r--src/css/common.css (renamed from css/common.css)0
-rw-r--r--src/css/explorer.css (renamed from css/explorer.css)0
-rw-r--r--src/images/button.gif (renamed from images/button.gif)bin137 -> 137 bytes
-rw-r--r--src/images/close.gif (renamed from images/close.gif)bin70 -> 70 bytes
-rw-r--r--src/images/collapsed.gif (renamed from images/collapsed.gif)bin877 -> 877 bytes
-rw-r--r--src/images/error.gif (renamed from images/error.gif)bin907 -> 907 bytes
-rw-r--r--src/images/expanded.gif (renamed from images/expanded.gif)bin878 -> 878 bytes
-rw-r--r--src/images/maximize.gif (renamed from images/maximize.gif)bin843 -> 843 bytes
-rw-r--r--src/images/minimize.gif (renamed from images/minimize.gif)bin64 -> 64 bytes
-rw-r--r--src/images/normalize.gif (renamed from images/normalize.gif)bin845 -> 845 bytes
-rw-r--r--src/images/point.gif (renamed from images/point.gif)bin55 -> 55 bytes
-rw-r--r--src/images/resize.gif (renamed from images/resize.gif)bin74 -> 74 bytes
-rw-r--r--src/images/separator.gif (renamed from images/separator.gif)bin146 -> 146 bytes
-rw-r--r--src/images/submenu.gif (renamed from images/submenu.gif)bin56 -> 56 bytes
-rw-r--r--src/images/transparent.gif (renamed from images/transparent.gif)bin90 -> 90 bytes
-rw-r--r--src/images/warning.gif (renamed from images/warning.gif)bin276 -> 276 bytes
-rw-r--r--src/images/warning.png (renamed from images/warning.png)bin425 -> 425 bytes
-rw-r--r--src/images/window-title.gif (renamed from images/window-title.gif)bin275 -> 275 bytes
-rw-r--r--src/images/window.gif (renamed from images/window.gif)bin75 -> 75 bytes
-rw-r--r--src/js/editor/mxDefaultKeyHandler.js126
-rw-r--r--src/js/editor/mxDefaultPopupMenu.js300
-rw-r--r--src/js/editor/mxDefaultToolbar.js567
-rw-r--r--src/js/editor/mxEditor.js3220
-rw-r--r--src/js/handler/mxCellHighlight.js271
-rw-r--r--src/js/handler/mxCellMarker.js419
-rw-r--r--src/js/handler/mxCellTracker.js149
-rw-r--r--src/js/handler/mxConnectionHandler.js1969
-rw-r--r--src/js/handler/mxConstraintHandler.js308
-rw-r--r--src/js/handler/mxEdgeHandler.js1529
-rw-r--r--src/js/handler/mxEdgeSegmentHandler.js284
-rw-r--r--src/js/handler/mxElbowEdgeHandler.js248
-rw-r--r--src/js/handler/mxGraphHandler.js916
-rw-r--r--src/js/handler/mxKeyHandler.js402
-rw-r--r--src/js/handler/mxPanningHandler.js390
-rw-r--r--src/js/handler/mxRubberband.js348
-rw-r--r--src/js/handler/mxSelectionCellsHandler.js260
-rw-r--r--src/js/handler/mxTooltipHandler.js317
-rw-r--r--src/js/handler/mxVertexHandler.js753
-rw-r--r--src/js/index.txt316
-rw-r--r--src/js/io/mxCellCodec.js170
-rw-r--r--src/js/io/mxChildChangeCodec.js149
-rw-r--r--src/js/io/mxCodec.js531
-rw-r--r--src/js/io/mxCodecRegistry.js137
-rw-r--r--src/js/io/mxDefaultKeyHandlerCodec.js88
-rw-r--r--src/js/io/mxDefaultPopupMenuCodec.js54
-rw-r--r--src/js/io/mxDefaultToolbarCodec.js301
-rw-r--r--src/js/io/mxEditorCodec.js246
-rw-r--r--src/js/io/mxGenericChangeCodec.js64
-rw-r--r--src/js/io/mxGraphCodec.js28
-rw-r--r--src/js/io/mxGraphViewCodec.js197
-rw-r--r--src/js/io/mxModelCodec.js80
-rw-r--r--src/js/io/mxObjectCodec.js983
-rw-r--r--src/js/io/mxRootChangeCodec.js83
-rw-r--r--src/js/io/mxStylesheetCodec.js210
-rw-r--r--src/js/io/mxTerminalChangeCodec.js42
-rw-r--r--src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js206
-rw-r--r--src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js174
-rw-r--r--src/js/layout/hierarchical/model/mxGraphHierarchyModel.js685
-rw-r--r--src/js/layout/hierarchical/model/mxGraphHierarchyNode.js210
-rw-r--r--src/js/layout/hierarchical/mxHierarchicalLayout.js623
-rw-r--r--src/js/layout/hierarchical/stage/mxCoordinateAssignment.js1836
-rw-r--r--src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js25
-rw-r--r--src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js674
-rw-r--r--src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js131
-rw-r--r--src/js/layout/mxCircleLayout.js203
-rw-r--r--src/js/layout/mxCompactTreeLayout.js995
-rw-r--r--src/js/layout/mxCompositeLayout.js101
-rw-r--r--src/js/layout/mxEdgeLabelLayout.js165
-rw-r--r--src/js/layout/mxFastOrganicLayout.js591
-rw-r--r--src/js/layout/mxGraphLayout.js503
-rw-r--r--src/js/layout/mxParallelEdgeLayout.js198
-rw-r--r--src/js/layout/mxPartitionLayout.js240
-rw-r--r--src/js/layout/mxStackLayout.js381
-rw-r--r--src/js/model/mxCell.js806
-rw-r--r--src/js/model/mxCellPath.js163
-rw-r--r--src/js/model/mxGeometry.js277
-rw-r--r--src/js/model/mxGraphModel.js2622
-rw-r--r--src/js/mxClient.js643
-rw-r--r--src/js/shape/mxActor.js183
-rw-r--r--src/js/shape/mxArrow.js226
-rw-r--r--src/js/shape/mxCloud.js56
-rw-r--r--src/js/shape/mxConnector.js446
-rw-r--r--src/js/shape/mxCylinder.js319
-rw-r--r--src/js/shape/mxDoubleEllipse.js203
-rw-r--r--src/js/shape/mxEllipse.js132
-rw-r--r--src/js/shape/mxHexagon.js37
-rw-r--r--src/js/shape/mxImageShape.js405
-rw-r--r--src/js/shape/mxLabel.js427
-rw-r--r--src/js/shape/mxLine.js217
-rw-r--r--src/js/shape/mxMarker.js267
-rw-r--r--src/js/shape/mxPolyline.js146
-rw-r--r--src/js/shape/mxRectangleShape.js61
-rw-r--r--src/js/shape/mxRhombus.js172
-rw-r--r--src/js/shape/mxShape.js2045
-rw-r--r--src/js/shape/mxStencil.js1585
-rw-r--r--src/js/shape/mxStencilRegistry.js53
-rw-r--r--src/js/shape/mxStencilShape.js209
-rw-r--r--src/js/shape/mxSwimlane.js553
-rw-r--r--src/js/shape/mxText.js1811
-rw-r--r--src/js/shape/mxTriangle.js34
-rw-r--r--src/js/util/mxAnimation.js82
-rw-r--r--src/js/util/mxAutoSaveManager.js213
-rw-r--r--src/js/util/mxClipboard.js144
-rw-r--r--src/js/util/mxConstants.js1911
-rw-r--r--src/js/util/mxDictionary.js130
-rw-r--r--src/js/util/mxDivResizer.js151
-rw-r--r--src/js/util/mxDragSource.js594
-rw-r--r--src/js/util/mxEffects.js214
-rw-r--r--src/js/util/mxEvent.js1175
-rw-r--r--src/js/util/mxEventObject.js111
-rw-r--r--src/js/util/mxEventSource.js191
-rw-r--r--src/js/util/mxForm.js202
-rw-r--r--src/js/util/mxGuide.js364
-rw-r--r--src/js/util/mxImage.js40
-rw-r--r--src/js/util/mxImageBundle.js98
-rw-r--r--src/js/util/mxImageExport.js1412
-rw-r--r--src/js/util/mxLog.js410
-rw-r--r--src/js/util/mxMorphing.js239
-rw-r--r--src/js/util/mxMouseEvent.js241
-rw-r--r--src/js/util/mxObjectIdentity.js59
-rw-r--r--src/js/util/mxPanningManager.js262
-rw-r--r--src/js/util/mxPath.js314
-rw-r--r--src/js/util/mxPoint.js55
-rw-r--r--src/js/util/mxPopupMenu.js574
-rw-r--r--src/js/util/mxRectangle.js134
-rw-r--r--src/js/util/mxResources.js366
-rw-r--r--src/js/util/mxSession.js674
-rw-r--r--src/js/util/mxSvgCanvas2D.js1234
-rw-r--r--src/js/util/mxToolbar.js528
-rw-r--r--src/js/util/mxUndoManager.js229
-rw-r--r--src/js/util/mxUndoableEdit.js168
-rw-r--r--src/js/util/mxUrlConverter.js141
-rw-r--r--src/js/util/mxUtils.js3920
-rw-r--r--src/js/util/mxWindow.js1065
-rw-r--r--src/js/util/mxXmlCanvas2D.js715
-rw-r--r--src/js/util/mxXmlRequest.js425
-rw-r--r--src/js/view/mxCellEditor.js522
-rw-r--r--src/js/view/mxCellOverlay.js233
-rw-r--r--src/js/view/mxCellRenderer.js1480
-rw-r--r--src/js/view/mxCellState.js375
-rw-r--r--src/js/view/mxCellStatePreview.js223
-rw-r--r--src/js/view/mxConnectionConstraint.js42
-rw-r--r--src/js/view/mxEdgeStyle.js1302
-rw-r--r--src/js/view/mxGraph.js11176
-rw-r--r--src/js/view/mxGraphSelectionModel.js435
-rw-r--r--src/js/view/mxGraphView.js2545
-rw-r--r--src/js/view/mxLayoutManager.js375
-rw-r--r--src/js/view/mxMultiplicity.js257
-rw-r--r--src/js/view/mxOutline.js649
-rw-r--r--src/js/view/mxPerimeter.js484
-rw-r--r--src/js/view/mxPrintPreview.js801
-rw-r--r--src/js/view/mxSpaceManager.js460
-rw-r--r--src/js/view/mxStyleRegistry.js70
-rw-r--r--src/js/view/mxStylesheet.js266
-rw-r--r--src/js/view/mxSwimlaneManager.js449
-rw-r--r--src/js/view/mxTemporaryCellStates.js105
-rw-r--r--src/js/xcos/core/details.js (renamed from details.js)0
-rw-r--r--src/resources/editor.properties (renamed from resources/editor.properties)0
-rw-r--r--src/resources/graph.properties (renamed from resources/graph.properties)0
-rw-r--r--test.html2
169 files changed, 79538 insertions, 1568 deletions
diff --git a/images/icons48/column.png b/images/icons48/column.png
new file mode 100644
index 0000000..5ae2c24
--- /dev/null
+++ b/images/icons48/column.png
Binary files differ
diff --git a/images/icons48/earth.png b/images/icons48/earth.png
new file mode 100644
index 0000000..4493880
--- /dev/null
+++ b/images/icons48/earth.png
Binary files differ
diff --git a/images/icons48/gear.png b/images/icons48/gear.png
new file mode 100644
index 0000000..647d897
--- /dev/null
+++ b/images/icons48/gear.png
Binary files differ
diff --git a/images/icons48/keys.png b/images/icons48/keys.png
new file mode 100644
index 0000000..41828e4
--- /dev/null
+++ b/images/icons48/keys.png
Binary files differ
diff --git a/images/icons48/mail_new.png b/images/icons48/mail_new.png
new file mode 100644
index 0000000..16c6662
--- /dev/null
+++ b/images/icons48/mail_new.png
Binary files differ
diff --git a/images/icons48/server.png b/images/icons48/server.png
new file mode 100644
index 0000000..9621c6e
--- /dev/null
+++ b/images/icons48/server.png
Binary files differ
diff --git a/images/icons48/table.png b/images/icons48/table.png
new file mode 100644
index 0000000..d4df646
--- /dev/null
+++ b/images/icons48/table.png
Binary files differ
diff --git a/index.html b/index.html
index 2fde407..f21c095 100644
--- a/index.html
+++ b/index.html
@@ -14,12 +14,18 @@
font-size: 16px;
}
</style>
+
+ <!-- Sets the basepath for the library if not in same directory -->
+ <script type="text/javascript">
+ mxBasePath = 'src';
+ </script>
+
<!-- Loads and initializes the library -->
- <script type="text/javascript" src="mxClient.min.js"></script>
+ <script type="text/javascript" src="src/js/mxClient.js"></script>
<link rel="stylesheet" href="jquery/jquery-ui.css">
<script src="jquery/jquery-1.8.2.js"></script>
- <script type="text/javascript" src="details.js"></script>
+ <script type="text/javascript" src="src/js/xcos/core/details.js"></script>
<script type="text/javascript" src="json2.js"></script>
<!-- Example code -->
<script type="text/javascript">
@@ -343,7 +349,7 @@
// Preload all images
- var dir = ["blocks","images"];
+ var dir = ["blocks","images","images/icons48"];
var fileextension = ".";
var blockImages = [];
$.each(dir, function (index, value) {
diff --git a/mxClient.min.js b/mxClient.min.js
deleted file mode 100644
index 80979cd..0000000
--- a/mxClient.min.js
+++ /dev/null
@@ -1,1564 +0,0 @@
-var mxClient={VERSION:"1.10.4.1",IS_IE:0<=navigator.userAgent.indexOf("MSIE"),IS_IE6:0<=navigator.userAgent.indexOf("MSIE 6"),IS_QUIRKS:0<=navigator.userAgent.indexOf("MSIE")&&(null==document.documentMode||5==document.documentMode),IS_NS:0<=navigator.userAgent.indexOf("Mozilla/")&&0>navigator.userAgent.indexOf("MSIE"),IS_OP:0<=navigator.userAgent.indexOf("Opera/"),IS_OT: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."),IS_SF:0<=navigator.userAgent.indexOf("AppleWebKit/")&&0>navigator.userAgent.indexOf("Chrome/"),IS_GC:0<=navigator.userAgent.indexOf("Chrome/"),IS_MT:0<=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."),IS_SVG:0<=navigator.userAgent.indexOf("Firefox/")||0<=navigator.userAgent.indexOf("Iceweasel/")||0<=navigator.userAgent.indexOf("Seamonkey/")||0<=navigator.userAgent.indexOf("Iceape/")||0<=navigator.userAgent.indexOf("Galeon/")||0<=navigator.userAgent.indexOf("Epiphany/")||0<=
-navigator.userAgent.indexOf("AppleWebKit/")||0<=navigator.userAgent.indexOf("Gecko/")||0<=navigator.userAgent.indexOf("Opera/"),NO_FO:0<=navigator.userAgent.indexOf("Firefox/1.")||0<=navigator.userAgent.indexOf("Iceweasel/1.")||0<=navigator.userAgent.indexOf("Firefox/2.")||0<=navigator.userAgent.indexOf("Iceweasel/2.")||0<=navigator.userAgent.indexOf("SeaMonkey/1.")||0<=navigator.userAgent.indexOf("Iceape/1.")||0<=navigator.userAgent.indexOf("Camino/1.")||0<=navigator.userAgent.indexOf("Epiphany/2.")||
-0<=navigator.userAgent.indexOf("Opera/")||0<=navigator.userAgent.indexOf("MSIE")||0<=navigator.userAgent.indexOf("Mozilla/2."),IS_VML:"MICROSOFT INTERNET EXPLORER"==navigator.appName.toUpperCase(),IS_MAC:0<navigator.userAgent.toUpperCase().indexOf("MACINTOSH"),IS_TOUCH:0<navigator.userAgent.toUpperCase().indexOf("IPAD")||0<navigator.userAgent.toUpperCase().indexOf("IPOD")||0<navigator.userAgent.toUpperCase().indexOf("IPHONE")||0<navigator.userAgent.toUpperCase().indexOf("ANDROID"),IS_LOCAL:0>document.location.href.indexOf("http://")&&
-0>document.location.href.indexOf("https://"),isBrowserSupported:function(){return mxClient.IS_VML||mxClient.IS_SVG},link:function(a,b,c){c=c||document;if(mxClient.IS_IE6)c.write('<link rel="'+a+'" href="'+b+'" charset="ISO-8859-1" type="text/css"/>');else{var d=c.createElement("link");d.setAttribute("rel",a);d.setAttribute("href",b);d.setAttribute("charset","ISO-8859-1");d.setAttribute("type","text/css");c.getElementsByTagName("head")[0].appendChild(d)}},include:function(a){document.write('<script src="'+
-a+'"><\/script>')},dispose:function(){for(var a=0;a<mxEvent.objects.length;a++)null!=mxEvent.objects[a].mxListenerList&&mxEvent.removeAllListeners(mxEvent.objects[a])}};"undefined"==typeof mxLoadResources&&(mxLoadResources=!0);"undefined"==typeof mxLoadStylesheets&&(mxLoadStylesheets=!0);"undefined"!=typeof mxBasePath&&0<mxBasePath.length?("/"==mxBasePath.substring(mxBasePath.length-1)&&(mxBasePath=mxBasePath.substring(0,mxBasePath.length-1)),mxClient.basePath=mxBasePath):mxClient.basePath=".";
-"undefined"!=typeof mxImageBasePath&&0<mxImageBasePath.length?("/"==mxImageBasePath.substring(mxImageBasePath.length-1)&&(mxImageBasePath=mxImageBasePath.substring(0,mxImageBasePath.length-1)),mxClient.imageBasePath=mxImageBasePath):mxClient.imageBasePath=mxClient.basePath+"/images";mxClient.language="undefined"!=typeof mxLanguage?mxLanguage:mxClient.IS_IE?navigator.userLanguage:navigator.language;mxClient.defaultLanguage="undefined"!=typeof mxDefaultLanguage?mxDefaultLanguage:"en";
-mxLoadStylesheets&&mxClient.link("stylesheet",mxClient.basePath+"/css/common.css");"undefined"!=typeof mxLanguages&&(mxClient.languages=mxLanguages);
-if(mxClient.IS_IE){if(9<=document.documentMode)mxClient.IS_VML=!1,mxClient.IS_SVG=!0;else{8==document.documentMode?(document.namespaces.add("v","urn:schemas-microsoft-com:vml","#default#VML"),document.namespaces.add("o","urn:schemas-microsoft-com:office:office","#default#VML")):(document.namespaces.add("v","urn:schemas-microsoft-com:vml"),document.namespaces.add("o","urn:schemas-microsoft-com:office:office"));var ss=document.createStyleSheet();ss.cssText="v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}";
-mxLoadStylesheets&&mxClient.link("stylesheet",mxClient.basePath+"/css/explorer.css")}window.attachEvent("onunload",mxClient.dispose)}
-var mxLog={consoleName:"Console",TRACE:!1,DEBUG:!0,WARN:!0,buffer:"",init:function(){if(mxLog.window==null&&document.body!=null){var a=mxLog.consoleName+" - mxGraph "+mxClient.VERSION,b=document.createElement("table");b.setAttribute("width","100%");b.setAttribute("height","100%");var c=document.createElement("tbody"),d=document.createElement("tr"),e=document.createElement("td");e.style.verticalAlign="top";mxLog.textarea=document.createElement("textarea");mxLog.textarea.setAttribute("readOnly","true");
-mxLog.textarea.style.height="100%";mxLog.textarea.style.resize="none";mxLog.textarea.value=mxLog.buffer;mxLog.textarea.style.width=mxClient.IS_NS&&document.compatMode!="BackCompat"?"99%":"100%";e.appendChild(mxLog.textarea);d.appendChild(e);c.appendChild(d);d=document.createElement("tr");mxLog.td=document.createElement("td");mxLog.td.style.verticalAlign="top";mxLog.td.setAttribute("height","30px");d.appendChild(mxLog.td);c.appendChild(d);b.appendChild(c);mxLog.addButton("Info",function(){mxLog.info()});
-mxLog.addButton("DOM",function(){var a=mxUtils.getInnerHtml(document.body);mxLog.debug(a)});mxLog.addButton("Trace",function(){mxLog.TRACE=!mxLog.TRACE;mxLog.TRACE?mxLog.debug("Tracing enabled"):mxLog.debug("Tracing disabled")});mxLog.addButton("Copy",function(){try{mxUtils.copy(mxLog.textarea.value)}catch(a){mxUtils.alert(a)}});mxLog.addButton("Show",function(){try{mxUtils.popup(mxLog.textarea.value)}catch(a){mxUtils.alert(a)}});mxLog.addButton("Clear",function(){mxLog.textarea.value=""});d=c=0;
-if(typeof window.innerWidth==="number"){c=window.innerHeight;d=window.innerWidth}else{c=document.documentElement.clientHeight||document.body.clientHeight;d=document.body.clientWidth}mxLog.window=new mxWindow(a,b,Math.max(0,d-320),Math.max(0,c-210),300,160);mxLog.window.setMaximizable(true);mxLog.window.setScrollable(false);mxLog.window.setResizable(true);mxLog.window.setClosable(true);mxLog.window.destroyOnClose=false;if((mxClient.IS_NS||mxClient.IS_IE)&&!mxClient.IS_GC&&!mxClient.IS_SF&&document.compatMode!=
-"BackCompat"){var f=mxLog.window.getElement(),a=function(){mxLog.textarea.style.height=Math.max(0,f.offsetHeight-70)+"px"};mxLog.window.addListener(mxEvent.RESIZE_END,a);mxLog.window.addListener(mxEvent.MAXIMIZE,a);mxLog.window.addListener(mxEvent.NORMALIZE,a);mxLog.textarea.style.height="92px"}}},info:function(){mxLog.writeln(mxUtils.toString(navigator))},addButton:function(a,b){var c=document.createElement("button");mxUtils.write(c,a);mxEvent.addListener(c,"click",b);mxLog.td.appendChild(c)},isVisible:function(){return mxLog.window!=
-null?mxLog.window.isVisible():false},show:function(){mxLog.setVisible(true)},setVisible:function(a){mxLog.window==null&&mxLog.init();mxLog.window!=null&&mxLog.window.setVisible(a)},enter:function(a){if(mxLog.TRACE){mxLog.writeln("Entering "+a);return(new Date).getTime()}},leave:function(a,b){if(mxLog.TRACE){var c=b!=0?" ("+((new Date).getTime()-b)+" ms)":"";mxLog.writeln("Leaving "+a+c)}},debug:function(){mxLog.DEBUG&&mxLog.writeln.apply(this,arguments)},warn:function(){mxLog.WARN&&mxLog.writeln.apply(this,
-arguments)},write:function(){for(var a="",b=0;b<arguments.length;b++){a=a+arguments[b];b<arguments.length-1&&(a=a+" ")}if(mxLog.textarea!=null){mxLog.textarea.value=mxLog.textarea.value+a;if(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=mxLog.buffer+a},writeln:function(){for(var a="",b=0;b<arguments.length;b++){a=a+arguments[b];b<arguments.length-
-1&&(a=a+" ")}mxLog.write(a+"\n")}},mxObjectIdentity={FIELD_NAME:"mxObjectId",counter:0,get:function(a){if(typeof a=="object"&&a[mxObjectIdentity.FIELD_NAME]==null){var b=mxUtils.getFunctionName(a.constructor);a[mxObjectIdentity.FIELD_NAME]=b+"#"+mxObjectIdentity.counter++}return a[mxObjectIdentity.FIELD_NAME]},clear:function(a){typeof a=="object"&&delete a[mxObjectIdentity.FIELD_NAME]}};function mxDictionary(){this.clear()}mxDictionary.prototype.map=null;
-mxDictionary.prototype.clear=function(){this.map={}};mxDictionary.prototype.get=function(a){return this.map[mxObjectIdentity.get(a)]};mxDictionary.prototype.put=function(a,b){var c=mxObjectIdentity.get(a),d=this.map[c];this.map[c]=b;return d};mxDictionary.prototype.remove=function(a){var a=mxObjectIdentity.get(a),b=this.map[a];delete this.map[a];return b};mxDictionary.prototype.getKeys=function(){var a=[],b;for(b in this.map)a.push(b);return a};
-mxDictionary.prototype.getValues=function(){var a=[],b;for(b in this.map)a.push(this.map[b]);return a};mxDictionary.prototype.visit=function(a){for(var b in this.map)a(b,this.map[b])};
-var mxResources={resources:[],extension:".properties",resourcesEncoded:!0,loadDefaultBundle:!0,loadSpecialBundle:!0,isLanguageSupported:function(a){return mxClient.languages!=null?mxUtils.indexOf(mxClient.languages,a)>=0:true},getDefaultBundle:function(a,b){return mxResources.loadDefaultBundle||!mxResources.isLanguageSupported(b)?a+mxResources.extension:null},getSpecialBundle:function(a,b){if(mxClient.languages==null||!this.isLanguageSupported(b)){var c=b.indexOf("-");c>0&&(b=b.substring(0,c))}return mxResources.loadSpecialBundle&&
-mxResources.isLanguageSupported(b)&&b!=mxClient.defaultLanguage?a+"_"+b+mxResources.extension:null},add:function(a,b){b=b!=null?b:mxClient.language.toLowerCase();if(b!=mxConstants.NONE){var c=mxResources.getDefaultBundle(a,b);if(c!=null)try{var d=mxUtils.load(c);d.isReady()&&mxResources.parse(d.getText())}catch(e){}c=mxResources.getSpecialBundle(a,b);if(c!=null)try{d=mxUtils.load(c);d.isReady()&&mxResources.parse(d.getText())}catch(f){}}},parse:function(a){if(a!=null)for(var a=a.split("\n"),b=0;b<
-a.length;b++)if(a[b].charAt(0)!="#"){var c=a[b].indexOf("=");if(c>0){var d=a[b].substring(0,c),e=a[b].length;a[b].charCodeAt(e-1)==13&&e--;c=a[b].substring(c+1,e);if(this.resourcesEncoded){c=c.replace(/\\(?=u[a-fA-F\d]{4})/g,"%");mxResources.resources[d]=unescape(c)}else mxResources.resources[d]=c}}},get:function(a,b,c){a=mxResources.resources[a];a==null&&(a=c);if(a!=null&&b!=null){for(var c=[],d=null,e=0;e<a.length;e++){var f=a.charAt(e);if(f=="{")d="";else if(d!=null&&f=="}"){d=parseInt(d)-1;d>=
-0&&d<b.length&&c.push(b[d]);d=null}else d!=null?d=d+f:c.push(f)}a=c.join("")}return a}};function mxPoint(a,b){this.x=a!=null?a:0;this.y=b!=null?b:0}mxPoint.prototype.x=null;mxPoint.prototype.y=null;mxPoint.prototype.equals=function(a){return a.x==this.x&&a.y==this.y};mxPoint.prototype.clone=function(){return mxUtils.clone(this)};function mxRectangle(a,b,c,d){mxPoint.call(this,a,b);this.width=c!=null?c:0;this.height=d!=null?d:0}mxRectangle.prototype=new mxPoint;mxRectangle.prototype.constructor=mxRectangle;
-mxRectangle.prototype.width=null;mxRectangle.prototype.height=null;mxRectangle.prototype.setRect=function(a,b,c,d){this.x=a;this.y=b;this.width=c;this.height=d};mxRectangle.prototype.getCenterX=function(){return this.x+this.width/2};mxRectangle.prototype.getCenterY=function(){return this.y+this.height/2};
-mxRectangle.prototype.add=function(a){if(a!=null){var b=Math.min(this.x,a.x),c=Math.min(this.y,a.y),d=Math.max(this.x+this.width,a.x+a.width),a=Math.max(this.y+this.height,a.y+a.height);this.x=b;this.y=c;this.width=d-b;this.height=a-c}};mxRectangle.prototype.grow=function(a){this.x=this.x-a;this.y=this.y-a;this.width=this.width+2*a;this.height=this.height+2*a};mxRectangle.prototype.getPoint=function(){return new mxPoint(this.x,this.y)};
-mxRectangle.prototype.equals=function(a){return a.x==this.x&&a.y==this.y&&a.width==this.width&&a.height==this.height};
-var mxEffects={animateChanges:function(a,b,c){var d=0,e=function(){for(var g=false,h=0;h<b.length;h++){var k=b[h];if(k instanceof mxGeometryChange||k instanceof mxTerminalChange||k instanceof mxValueChange||k instanceof mxChildChange||k instanceof mxStyleChange){var i=a.getView().getState(k.cell||k.child,false);if(i!=null){g=true;if(k.constructor!=mxGeometryChange||a.model.isEdge(k.cell))mxUtils.setOpacity(i.shape.node,100*d/10);else{var l=a.getView().scale,m=(k.geometry.x-k.previous.x)*l,n=(k.geometry.y-
-k.previous.y)*l,o=(k.geometry.width-k.previous.width)*l,l=(k.geometry.height-k.previous.height)*l;if(d==0){i.x=i.x-m;i.y=i.y-n;i.width=i.width-o;i.height=i.height-l}else{i.x=i.x+m/10;i.y=i.y+n/10;i.width=i.width+o/10;i.height=i.height+l/10}a.cellRenderer.redraw(i);mxEffects.cascadeOpacity(a,k.cell,100*d/10)}}}}mxUtils.repaintGraph(a,new mxPoint(1,1));if(d<10&&g){d++;window.setTimeout(e,f)}else c!=null&&c()},f=30;e()},cascadeOpacity:function(a,b,c){for(var d=a.model.getChildCount(b),e=0;e<d;e++){var f=
-a.model.getChildAt(b,e),g=a.getView().getState(f);if(g!=null){mxUtils.setOpacity(g.shape.node,c);mxEffects.cascadeOpacity(a,f,c)}}b=a.model.getEdges(b);if(b!=null)for(e=0;e<b.length;e++){d=a.getView().getState(b[e]);d!=null&&mxUtils.setOpacity(d.shape.node,c)}},fadeOut:function(a,b,c,d,e,f){var d=d||40,e=e||30,g=b||100;mxUtils.setOpacity(a,g);if(f||f==null){var h=function(){g=Math.max(g-d,0);mxUtils.setOpacity(a,g);if(g>0)window.setTimeout(h,e);else{a.style.visibility="hidden";c&&a.parentNode&&a.parentNode.removeChild(a)}};
-window.setTimeout(h,e)}else{a.style.visibility="hidden";c&&a.parentNode&&a.parentNode.removeChild(a)}}},mxUtils={errorResource:"none"!=mxClient.language?"error":"",closeResource:"none"!=mxClient.language?"close":"",errorImage:mxClient.imageBasePath+"/error.gif",removeCursors:function(a){if(a.style!=null)a.style.cursor="";a=a.childNodes;if(a!=null)for(var b=a.length,c=0;c<b;c=c+1)mxUtils.removeCursors(a[c])},repaintGraph:function(a,b){if(mxClient.IS_GC||mxClient.IS_SF||mxClient.IS_OP){var c=a.container;
-if(c!=null&&b!=null&&(c.scrollLeft>0||c.scrollTop>0)){var d=document.createElement("div");d.style.position="absolute";d.style.left=b.x+"px";d.style.top=b.y+"px";d.style.width="1px";d.style.height="1px";c.appendChild(d);c.removeChild(d)}}},getCurrentStyle:function(){return mxClient.IS_IE?function(a){return a!=null?a.currentStyle:null}:function(a){return a!=null?window.getComputedStyle(a,""):null}}(),hasScrollbars:function(a){a=mxUtils.getCurrentStyle(a);return a!=null&&(a.overflow=="scroll"||a.overflow==
-"auto")},bind:function(a,b){return function(){return b.apply(a,arguments)}},eval:function(a){var b=null;if(a.indexOf("function")>=0)try{eval("var _mxJavaScriptExpression="+a);b=_mxJavaScriptExpression;_mxJavaScriptExpression=null}catch(c){mxLog.warn(c.message+" while evaluating "+a)}else try{b=eval(a)}catch(d){mxLog.warn(d.message+" while evaluating "+a)}return b},findNode:function(a,b,c){var d=a.getAttribute(b);if(d!=null&&d==c)return a;for(a=a.firstChild;a!=null;){d=mxUtils.findNode(a,b,c);if(d!=
-null)return d;a=a.nextSibling}return null},findNodeByAttribute:function(){return document.documentMode>=9?function(a,b,c){var d=null;if(a!=null)if(a.nodeType==mxConstants.NODETYPE_ELEMENT&&a.getAttribute(b)==c)d=a;else for(a=a.firstChild;a!=null&&d==null;){d=mxUtils.findNodeByAttribute(a,b,c);a=a.nextSibling}return d}:mxClient.IS_IE?function(a,b,c){return a==null?null:a.ownerDocument.selectSingleNode("//*[@"+b+"='"+c+"']")}:function(a,b,c){return a==null?null:a.ownerDocument.evaluate("//*[@"+b+"='"+
-c+"']",a.ownerDocument,null,XPathResult.ANY_TYPE,null).iterateNext()}}(),getFunctionName:function(a){var b=null;if(a!=null)if(a.name!=null)b=a.name;else{a=a.toString();for(b=9;a.charAt(b)==" ";)b++;var c=a.indexOf("(",b),b=a.substring(b,c)}return b},indexOf:function(a,b){if(a!=null&&b!=null)for(var c=0;c<a.length;c++)if(a[c]==b)return c;return-1},remove:function(a,b){var c=null;if(typeof b=="object")for(var d=mxUtils.indexOf(b,a);d>=0;){b.splice(d,1);c=a;d=mxUtils.indexOf(b,a)}for(var e in b)if(b[e]==
-a){delete b[e];c=a}return c},isNode:function(a,b,c,d){return a!=null&&!isNaN(a.nodeType)&&(b==null||a.nodeName.toLowerCase()==b.toLowerCase())?c==null||a.getAttribute(c)==d:false},getChildNodes:function(a,b){for(var b=b||mxConstants.NODETYPE_ELEMENT,c=[],d=a.firstChild;d!=null;){d.nodeType==b&&c.push(d);d=d.nextSibling}return c},createXmlDocument:function(){var a=null;document.implementation&&document.implementation.createDocument?a=document.implementation.createDocument("","",null):window.ActiveXObject&&
-(a=new ActiveXObject("Microsoft.XMLDOM"));return a},parseXml:function(){return mxClient.IS_IE&&(typeof document.documentMode==="undefined"||document.documentMode<9)?function(a){var b=mxUtils.createXmlDocument();b.async="false";b.loadXML(a);return b}:function(a){return(new DOMParser).parseFromString(a,"text/xml")}}(),clearSelection:function(){if(document.selection)return function(){document.selection.empty()};if(window.getSelection)return function(){window.getSelection().removeAllRanges()}}(),getPrettyXml:function(a,
-b,c){var d=[];if(a!=null){b=b||" ";c=c||"";if(a.nodeType==mxConstants.NODETYPE_TEXT)d.push(a.nodeValue);else{d.push(c+"<"+a.nodeName);var e=a.attributes;if(e!=null)for(var f=0;f<e.length;f++){var g=mxUtils.htmlEntities(e[f].nodeValue);d.push(" "+e[f].nodeName+'="'+g+'"')}e=a.firstChild;if(e!=null){for(d.push(">\n");e!=null;){d.push(mxUtils.getPrettyXml(e,b,c+b));e=e.nextSibling}d.push(c+"</"+a.nodeName+">\n")}else d.push("/>\n")}}return d.join("")},removeWhitespace:function(a,b){for(var c=b?a.previousSibling:
-a.nextSibling;c!=null&&c.nodeType==mxConstants.NODETYPE_TEXT;){var d=b?c.previousSibling:c.nextSibling,e=mxUtils.getTextContent(c);mxUtils.trim(e).length==0&&c.parentNode.removeChild(c);c=d}},htmlEntities:function(a,b){a=(a||"").replace(/&/g,"&amp;");a=a.replace(/"/g,"&quot;");a=a.replace(/\'/g,"&#39;");a=a.replace(/</g,"&lt;");a=a.replace(/>/g,"&gt;");if(b==null||b)a=a.replace(/\n/g,"&#xa;");return a},isVml:function(a){return a!=null&&a.tagUrn=="urn:schemas-microsoft-com:vml"},getXml:function(a,
-b){var c="";if(a!=null){c=a.xml;c=c==null?a.innerHTML?a.innerHTML:(new XMLSerializer).serializeToString(a):c.replace(/\r\n\t[\t]*/g,"").replace(/>\r\n/g,">").replace(/\r\n/g,"\n")}return c=c.replace(/\n/g,b||"&#xa;")},getTextContent:function(a){var b="";if(a!=null){if(a.firstChild!=null)a=a.firstChild;b=a.nodeValue||""}return b},getInnerHtml:function(){return mxClient.IS_IE?function(a){return a!=null?a.innerHTML:""}:function(a){return a!=null?(new XMLSerializer).serializeToString(a):""}}(),getOuterHtml:function(){return mxClient.IS_IE?
-function(a){if(a!=null){if(a.outerHTML!=null)return a.outerHTML;var b=[];b.push("<"+a.nodeName);var c=a.attributes;if(c!=null)for(var d=0;d<c.length;d++){var e=c[d].nodeValue;if(e!=null&&e.length>0){b.push(" ");b.push(c[d].nodeName);b.push('="');b.push(e);b.push('"')}}if(a.innerHTML.length==0)b.push("/>");else{b.push(">");b.push(a.innerHTML);b.push("</"+a.nodeName+">")}return b.join("")}return""}:function(a){return a!=null?(new XMLSerializer).serializeToString(a):""}}(),write:function(a,b){var c=
-a.ownerDocument.createTextNode(b);a!=null&&a.appendChild(c);return c},writeln:function(a,b){var c=a.ownerDocument.createTextNode(b);if(a!=null){a.appendChild(c);a.appendChild(document.createElement("br"))}return c},br:function(a,b){for(var b=b||1,c=null,d=0;d<b;d++)if(a!=null){c=a.ownerDocument.createElement("br");a.appendChild(c)}return c},button:function(a,b,c){c=c!=null?c:document;c=c.createElement("button");mxUtils.write(c,a);mxEvent.addListener(c,"click",function(a){b(a)});return c},para:function(a,
-b){var c=document.createElement("p");mxUtils.write(c,b);a!=null&&a.appendChild(c);return c},linkAction:function(a,b,c,d,e){return mxUtils.link(a,b,function(){c.execute(d)},e)},linkInvoke:function(a,b,c,d,e,f){return mxUtils.link(a,b,function(){c[d](e)},f)},link:function(a,b,c,d){var e=document.createElement("span");e.style.color="blue";e.style.textDecoration="underline";e.style.cursor="pointer";if(d!=null)e.style.paddingLeft=d+"px";mxEvent.addListener(e,"click",c);mxUtils.write(e,b);a!=null&&a.appendChild(e);
-return e},fit:function(a){var b=parseInt(a.offsetLeft),c=parseInt(a.offsetWidth),d=document.body,e=document.documentElement,f=(d.scrollLeft||e.scrollLeft)+(d.clientWidth||e.clientWidth);if(b+c>f)a.style.left=Math.max(d.scrollLeft||e.scrollLeft,f-c)+"px";b=parseInt(a.offsetTop);c=parseInt(a.offsetHeight);f=(d.scrollTop||e.scrollTop)+Math.max(d.clientHeight||0,e.clientHeight);if(b+c>f)a.style.top=Math.max(d.scrollTop||e.scrollTop,f-c)+"px"},open:function(a){if(mxClient.IS_NS){try{netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect")}catch(b){mxUtils.alert("Permission to read file denied.");
-return""}var c=Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);c.initWithPath(a);if(!c.exists()){mxUtils.alert("File not found.");return""}a=Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);a.init(c,1,4,null);c=Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);c.init(a);return c.read(c.available())}c=(new ActiveXObject("Scripting.FileSystemObject")).OpenTextFile(a,
-1);a=c.readAll();c.close();return a},save:function(a,b){if(mxClient.IS_NS){try{netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect")}catch(c){mxUtils.alert("Permission to write file denied.");return}var d=Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);d.initWithPath(a);d.exists()||d.create(0,420);var e=Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);e.init(d,
-34,4,null);e.write(b,b.length);e.flush();e.close()}else{d=(new ActiveXObject("Scripting.FileSystemObject")).CreateTextFile(a,true);d.Write(b);d.Close()}},saveAs:function(a){var b=document.createElement("iframe");b.setAttribute("src","");b.style.visibility="hidden";document.body.appendChild(b);try{if(mxClient.IS_NS){var c=b.contentDocument;c.open();c.write(a);c.close();try{netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");b.focus();saveDocument(c)}catch(d){mxUtils.alert("Permission to save document denied.")}}else{c=
-b.contentWindow.document;c.write(a);c.execCommand("SaveAs",false,document.location)}}finally{document.body.removeChild(b)}},copy:function(a){if(window.clipboardData)window.clipboardData.setData("Text",a);else{netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");var b=Components.classes["@mozilla.org/widget/clipboard;1"].createInstance(Components.interfaces.nsIClipboard);if(b){var c=Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
-if(c){c.addDataFlavor("text/unicode");var d=Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);d.data=a;c.setTransferData("text/unicode",d,a.length*2);b.setData(c,null,Components.interfaces.nsIClipboard.kGlobalClipboard)}}}},load:function(a){a=new mxXmlRequest(a,null,"GET",false);a.send();return a},get:function(a,b,c){return(new mxXmlRequest(a,null,"GET")).send(b,c)},post:function(a,b,c,d){return(new mxXmlRequest(a,b)).send(c,d)},submit:function(a,
-b,c,d){return(new mxXmlRequest(a,b)).simulate(c,d)},loadInto:function(a,b,c){mxClient.IS_IE?b.onreadystatechange=function(){b.readyState==4&&c()}:b.addEventListener("load",c,false);b.load(a)},getValue:function(a,b,c){a=a!=null?a[b]:null;a==null&&(a=c);return a},getNumber:function(a,b,c){a=a!=null?a[b]:null;a==null&&(a=c||0);return Number(a)},getColor:function(a,b,c){a=a!=null?a[b]:null;a==null?a=c:a==mxConstants.NONE&&(a=null);return a},clone:function(a,b,c){var c=c!=null?c:false,d=null;if(a!=null&&
-typeof a.constructor=="function"){var d=new a.constructor,e;for(e in a)if(e!=mxObjectIdentity.FIELD_NAME&&(b==null||mxUtils.indexOf(b,e)<0))d[e]=!c&&typeof a[e]=="object"?mxUtils.clone(a[e]):a[e]}return d},equalPoints:function(a,b){if(a==null&&b!=null||a!=null&&b==null||a!=null&&b!=null&&a.length!=b.length)return false;if(a!=null&&b!=null)for(var c=0;c<a.length;c++)if(a[c]==b[c]||a[c]!=null&&!a[c].equals(b[c]))return false;return true},equalEntries:function(a,b){if(a==null&&b!=null||a!=null&&b==null||
-a!=null&&b!=null&&a.length!=b.length)return false;if(a!=null&&b!=null)for(var c in a)if(a[c]!=b[c])return false;return true},extend:function(a,b){var c=function(){};c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a},toString:function(a){var b="",c;for(c in a)try{if(a[c]==null)b=b+(c+" = [null]\n");else if(typeof a[c]=="function")b=b+(c+" => [Function]\n");else if(typeof a[c]=="object")var d=mxUtils.getFunctionName(a[c].constructor),b=b+(c+" => ["+d+"]\n");else b=b+(c+" = "+a[c]+
-"\n")}catch(e){b=b+(c+"="+e.message)}return b},toRadians:function(a){return Math.PI*a/180},arcToCurves:function(a,b,c,d,e,f,g,h,k){h=h-a;k=k-b;if(c===0||d===0)return n;var c=Math.abs(c),d=Math.abs(d),i=-h/2,l=-k/2,m=Math.cos(e*Math.PI/180),n=Math.sin(e*Math.PI/180),e=m*i+n*l,i=-1*n*i+m*l,l=e*e,o=i*i,p=c*c,q=d*d,t=l/p+o/q;if(t>1){c=Math.sqrt(t)*c;d=Math.sqrt(t)*d;f=0}else{t=1;f===g&&(t=-1);f=t*Math.sqrt((p*q-p*o-q*l)/(p*o+q*l))}l=f*c*i/d;o=-1*f*d*e/c;h=m*l-n*o+h/2;k=n*l+m*o+k/2;p=Math.atan2((i-o)/
-d,(e-l)/c)-Math.atan2(0,1);f=p>=0?p:2*Math.PI+p;p=Math.atan2((-i-o)/d,(-e-l)/c)-Math.atan2((i-o)/d,(e-l)/c);e=p>=0?p:2*Math.PI+p;g==0&&e>0?e=e-2*Math.PI:g!=0&&e<0&&(e=e+2*Math.PI);for(var g=e*2/Math.PI,g=Math.ceil(g<0?-1*g:g),e=e/g,i=8/3*Math.sin(e/4)*Math.sin(e/4)/Math.sin(e/2),l=m*c,m=m*d,c=n*c,d=n*d,u=Math.cos(f),v=Math.sin(f),o=-i*(l*v+d*u),p=-i*(c*v-m*u),t=q=0,n=[],w=0;w<g;++w){var f=f+e,u=Math.cos(f),v=Math.sin(f),q=l*u-d*v+h,t=c*u+m*v+k,r=-i*(l*v+d*u),u=-i*(c*v-m*u),v=w*6;n[v]=Number(o+a);
-n[v+1]=Number(p+b);n[v+2]=Number(q-r+a);n[v+3]=Number(t-u+b);n[v+4]=Number(q+a);n[v+5]=Number(t+b);o=q+r;p=t+u}return n},getBoundingBox:function(a,b){var c=null;if(a!=null&&b!=null&&b!=0){var d=mxUtils.toRadians(b),c=Math.cos(d),e=Math.sin(d),f=new mxPoint(a.x+a.width/2,a.y+a.height/2),g=new mxPoint(a.x,a.y),d=new mxPoint(a.x+a.width,a.y),h=new mxPoint(d.x,a.y+a.height),k=new mxPoint(a.x,h.y),g=mxUtils.getRotatedPoint(g,c,e,f),d=mxUtils.getRotatedPoint(d,c,e,f),h=mxUtils.getRotatedPoint(h,c,e,f),
-k=mxUtils.getRotatedPoint(k,c,e,f),c=new mxRectangle(g.x,g.y,0,0);c.add(new mxRectangle(d.x,d.y,0,0));c.add(new mxRectangle(h.x,h.y,0,0));c.add(new mxRectangle(k.x,k.y,0,0))}return c},getRotatedPoint:function(a,b,c,d){var d=d!=null?d:new mxPoint,e=a.x-d.x,a=a.y-d.y;return new mxPoint(e*b-a*c+d.x,a*b+e*c+d.y)},getPortConstraints:function(a,b,c,d){a=mxUtils.getValue(a.style,mxConstants.STYLE_PORT_CONSTRAINT,null);if(a==null)return d;d=a.toString();a=mxConstants.DIRECTION_MASK_NONE;d.indexOf(mxConstants.DIRECTION_NORTH)>=
-0&&(a=a|mxConstants.DIRECTION_MASK_NORTH);d.indexOf(mxConstants.DIRECTION_WEST)>=0&&(a=a|mxConstants.DIRECTION_MASK_WEST);d.indexOf(mxConstants.DIRECTION_SOUTH)>=0&&(a=a|mxConstants.DIRECTION_MASK_SOUTH);d.indexOf(mxConstants.DIRECTION_EAST)>=0&&(a=a|mxConstants.DIRECTION_MASK_EAST);return a},reversePortConstraints:function(a){var b=0,b=(a&mxConstants.DIRECTION_MASK_WEST)<<3,b=b|(a&mxConstants.DIRECTION_MASK_NORTH)<<1,b=b|(a&mxConstants.DIRECTION_MASK_SOUTH)>>1;return b=b|(a&mxConstants.DIRECTION_MASK_EAST)>>
-3},findNearestSegment:function(a,b,c){var d=-1;if(a.absolutePoints.length>0)for(var e=a.absolutePoints[0],f=null,g=1;g<a.absolutePoints.length;g++){var h=a.absolutePoints[g],e=mxUtils.ptSegDistSq(e.x,e.y,h.x,h.y,b,c);if(f==null||e<f){f=e;d=g-1}e=h}return d},rectangleIntersectsSegment:function(a,b,c){var d=a.y,e=a.x,f=d+a.height,g=e+a.width,a=b.x,h=c.x;if(b.x>c.x){a=c.x;h=b.x}h>g&&(h=g);a<e&&(a=e);if(a>h)return false;var e=b.y,g=c.y,k=c.x-b.x;if(Math.abs(k)>1.0E-7){c=(c.y-b.y)/k;b=b.y-c*b.x;e=c*a+
-b;g=c*h+b}if(e>g){b=g;g=e;e=b}g>f&&(g=f);e<d&&(e=d);return e>g?false:true},contains:function(a,b,c){return a.x<=b&&a.x+a.width>=b&&a.y<=c&&a.y+a.height>=c},intersects:function(a,b){var c=a.width,d=a.height,e=b.width,f=b.height;if(e<=0||f<=0||c<=0||d<=0)return false;var g=a.x,h=a.y,k=b.x,i=b.y,e=e+k,f=f+i,c=c+g,d=d+h;return(e<k||e>g)&&(f<i||f>h)&&(c<g||c>k)&&(d<h||d>i)},intersectsHotspot:function(a,b,c,d,e,f){d=d!=null?d:1;e=e!=null?e:0;f=f!=null?f:0;if(d>0){var g=a.getCenterX(),h=a.getCenterY(),k=
-a.width,i=a.height,l=mxUtils.getValue(a.style,mxConstants.STYLE_STARTSIZE)*a.view.scale;if(l>0)if(mxUtils.getValue(a.style,mxConstants.STYLE_HORIZONTAL,true)){h=a.y+l/2;i=l}else{g=a.x+l/2;k=l}k=Math.max(e,k*d);i=Math.max(e,i*d);if(f>0){k=Math.min(k,f);i=Math.min(i,f)}a=new mxRectangle(g-k/2,h-i/2,k,i);return mxUtils.contains(a,b,c)}return true},getOffset:function(a,b){var c=0,d=0;if(b!=null&&b)var e=document.body,f=document.documentElement,c=c+(e.scrollLeft||f.scrollLeft),d=d+(e.scrollTop||f.scrollTop);
-for(;a.offsetParent;){c=c+a.offsetLeft;d=d+a.offsetTop;a=a.offsetParent}return new mxPoint(c,d)},getScrollOrigin:function(a){for(var b=document.body,c=document.documentElement,d=new mxPoint(b.scrollLeft||c.scrollLeft,b.scrollTop||c.scrollTop);a!=null&&a!=b&&a!=c;){if(!isNaN(a.scrollLeft)&&!isNaN(a.scrollTop)){d.x=d.x+a.scrollLeft;d.y=d.y+a.scrollTop}a=a.parentNode}return d},convertPoint:function(a,b,c){var d=mxUtils.getScrollOrigin(a),a=mxUtils.getOffset(a);a.x=a.x-d.x;a.y=a.y-d.y;return new mxPoint(b-
-a.x,c-a.y)},ltrim:function(a,b){return a.replace(RegExp("^["+(b||"\\s")+"]+","g"),"")},rtrim:function(a,b){return a.replace(RegExp("["+(b||"\\s")+"]+$","g"),"")},trim:function(a,b){return mxUtils.ltrim(mxUtils.rtrim(a,b),b)},isNumeric:function(a){return a!=null&&(a.length==null||a.length>0&&a.indexOf("0x")<0&&a.indexOf("0X")<0)&&!isNaN(a)},mod:function(a,b){return(a%b+b)%b},intersection:function(a,b,c,d,e,f,g,h){var k=(h-f)*(c-a)-(g-e)*(d-b),g=((g-e)*(b-f)-(h-f)*(a-e))/k,e=((c-a)*(b-f)-(d-b)*(a-e))/
-k;return g>=0&&g<=1&&e>=0&&e<=1?new mxPoint(a+g*(c-a),b+g*(d-b)):null},ptSegDistSq:function(a,b,c,d,e,f){c=c-a;d=d-b;e=e-a;f=f-b;if(e*c+f*d<=0)c=0;else{e=c-e;f=d-f;a=e*c+f*d;c=a<=0?0:a*a/(c*c+d*d)}e=e*e+f*f-c;e<0&&(e=0);return e},relativeCcw:function(a,b,c,d,e,f){c=c-a;d=d-b;e=e-a;f=f-b;a=e*d-f*c;if(a==0){a=e*c+f*d;if(a>0){a=(e-c)*c+(f-d)*d;a<0&&(a=0)}}return a<0?-1:a>0?1:0},animateChanges:function(a,b){mxEffects.animateChanges.apply(this,arguments)},cascadeOpacity:function(a,b,c){mxEffects.cascadeOpacity.apply(this,
-arguments)},fadeOut:function(a,b,c,d,e,f){mxEffects.fadeOut.apply(this,arguments)},setOpacity:function(a,b){mxUtils.isVml(a)?a.style.filter=b>=100?null:"alpha(opacity="+b/5+")":mxClient.IS_IE&&(typeof document.documentMode==="undefined"||document.documentMode<9)?a.style.filter=b>=100?null:"alpha(opacity="+b+")":a.style.opacity=b/100},createImage:function(a){var b=null;if(mxClient.IS_IE6&&document.compatMode!="CSS1Compat"){b=document.createElement("v:image");b.setAttribute("src",a);b.style.borderStyle=
-"none"}else{b=document.createElement("img");b.setAttribute("src",a);b.setAttribute("border","0")}return b},sortCells:function(a,b){var b=b!=null?b:true,c=new mxDictionary;a.sort(function(a,e){var f=c.get(a);if(f==null){f=mxCellPath.create(a).split(mxCellPath.PATH_SEPARATOR);c.put(a,f)}var g=c.get(e);if(g==null){g=mxCellPath.create(e).split(mxCellPath.PATH_SEPARATOR);c.put(e,g)}f=mxCellPath.compare(f,g);return f==0?0:f>0==b?1:-1});return a},getStylename:function(a){if(a!=null){a=a.split(";")[0];if(a.indexOf("=")<
-0)return a}return""},getStylenames:function(a){var b=[];if(a!=null)for(var a=a.split(";"),c=0;c<a.length;c++)a[c].indexOf("=")<0&&b.push(a[c]);return b},indexOfStylename:function(a,b){if(a!=null&&b!=null)for(var c=a.split(";"),d=0,e=0;e<c.length;e++){if(c[e]==b)return d;d=d+(c[e].length+1)}return-1},addStylename:function(a,b){if(mxUtils.indexOfStylename(a,b)<0){a==null?a="":a.length>0&&a.charAt(a.length-1)!=";"&&(a=a+";");a=a+b}return a},removeStylename:function(a,b){var c=[];if(a!=null)for(var d=
-a.split(";"),e=0;e<d.length;e++)d[e]!=b&&c.push(d[e]);return c.join(";")},removeAllStylenames:function(a){var b=[];if(a!=null)for(var a=a.split(";"),c=0;c<a.length;c++)a[c].indexOf("=")>=0&&b.push(a[c]);return b.join(";")},setCellStyles:function(a,b,c,d){if(b!=null&&b.length>0){a.beginUpdate();try{for(var e=0;e<b.length;e++)if(b[e]!=null){var f=mxUtils.setStyle(a.getStyle(b[e]),c,d);a.setStyle(b[e],f)}}finally{a.endUpdate()}}},setStyle:function(a,b,c){var d=c!=null&&(typeof c.length=="undefined"||
-c.length>0);if(a==null||a.length==0)d&&(a=b+"="+c);else{var e=a.indexOf(b+"=");if(e<0){if(d){d=a.charAt(a.length-1)==";"?"":";";a=a+d+b+"="+c}}else{b=d?b+"="+c:"";c=a.indexOf(";",e);d||c++;a=a.substring(0,e)+b+(c>e?a.substring(c):"")}}return a},setCellStyleFlags:function(a,b,c,d,e){if(b!=null&&b.length>0){a.beginUpdate();try{for(var f=0;f<b.length;f++)if(b[f]!=null){var g=mxUtils.setStyleFlag(a.getStyle(b[f]),c,d,e);a.setStyle(b[f],g)}}finally{a.endUpdate()}}},setStyleFlag:function(a,b,c,d){if(a==
-null||a.length==0)a=d||d==null?b+"="+c:b+"=0";else{var e=a.indexOf(b+"=");if(e<0){e=a.charAt(a.length-1)==";"?"":";";a=d||d==null?a+e+b+"="+c:a+e+b+"=0"}else var f=a.indexOf(";",e),g="",g=f<0?a.substring(e+b.length+1):a.substring(e+b.length+1,f),g=d==null?parseInt(g)^c:d?parseInt(g)|c:parseInt(g)&~c,a=a.substring(0,e)+b+"="+g+(f>=0?a.substring(f):"")}return a},getSizeForString:function(a,b,c){var d=document.createElement("div");d.style.fontSize=(b||mxConstants.DEFAULT_FONTSIZE)+"px";d.style.fontFamily=
-c||mxConstants.DEFAULT_FONTFAMILY;d.style.position="absolute";d.style.display="inline";d.style.visibility="hidden";d.innerHTML=a;document.body.appendChild(d);a=new mxRectangle(0,0,d.offsetWidth,d.offsetHeight);document.body.removeChild(d);return a},getViewXml:function(a,b,c,d,e){d=d!=null?d:0;e=e!=null?e:0;b=b!=null?b:1;c==null&&(c=[a.getModel().getRoot()]);var f=a.getView(),g=null,h=f.isEventsEnabled();f.setEventsEnabled(false);var k=f.drawPane,i=f.overlayPane;if(a.dialect==mxConstants.DIALECT_SVG){f.drawPane=
-document.createElementNS(mxConstants.NS_SVG,"g");f.canvas.appendChild(f.drawPane);f.overlayPane=document.createElementNS(mxConstants.NS_SVG,"g")}else{f.drawPane=f.drawPane.cloneNode(false);f.canvas.appendChild(f.drawPane);f.overlayPane=f.overlayPane.cloneNode(false)}f.canvas.appendChild(f.overlayPane);var l=f.getTranslate();f.translate=new mxPoint(d,e);b=new mxTemporaryCellStates(a.getView(),b,c);try{g=(new mxCodec).encode(a.getView())}finally{b.destroy();f.translate=l;f.canvas.removeChild(f.drawPane);
-f.canvas.removeChild(f.overlayPane);f.drawPane=k;f.overlayPane=i;f.setEventsEnabled(h)}return g},getScaleForPageCount:function(a,b,c,d){if(a<1)return 1;var c=c!=null?c:mxConstants.PAGE_FORMAT_A4_PORTRAIT,d=d!=null?d:0,e=c.width-d*2,c=c.height-d*2,d=b.getGraphBounds().clone(),b=b.getView().getScale();d.width=d.width/b;d.height=d.height/b;var b=d.width,c=b/d.height/(e/c),d=Math.sqrt(a),f=Math.sqrt(c),c=d*f,d=d/f;if(c<1&&d>a)var g=d/a,d=a,c=c/g;if(d<1&&c>a){g=c/a;c=a;d=d/g}g=Math.ceil(c)*Math.ceil(d);
-for(f=0;g>a;){var g=Math.floor(c)/c,h=Math.floor(d)/d;g==1&&(g=Math.floor(c-1)/c);h==1&&(h=Math.floor(d-1)/d);g=g>h?g:h;c=c*g;d=d*g;g=Math.ceil(c)*Math.ceil(d);f++;if(f>10)break}return e*c/b*0.99999},show:function(a,b,c,d){c=c!=null?c:0;d=d!=null?d:0;b==null?b=window.open().document:b.open();var e=a.getGraphBounds(),c=-e.x+c,d=-e.y+d;if(mxClient.IS_IE){for(var e="<html><head>",f=document.getElementsByTagName("base"),g=0;g<f.length;g++)e=e+f[g].outerHTML;e=e+"<style>";for(g=0;g<document.styleSheets.length;g++)try{e=
-e+document.styleSheets(g).cssText}catch(h){}e=e+"</style></head><body>"+a.container.innerHTML;b.writeln(e+"</body><html>");b.close();a=b.body.getElementsByTagName("DIV")[0];if(a!=null){a.style.position="absolute";a.style.left=c+"px";a.style.top=d+"px"}}else{b.writeln("<html");b.writeln("<head>");f=document.getElementsByTagName("base");for(g=0;g<f.length;g++)b.writeln(mxUtils.getOuterHtml(f[g]));f=document.getElementsByTagName("link");for(g=0;g<f.length;g++)b.writeln(mxUtils.getOuterHtml(f[g]));f=
-document.getElementsByTagName("style");for(g=0;g<f.length;g++)b.writeln(mxUtils.getOuterHtml(f[g]));b.writeln("</head>");b.writeln("</html>");b.close();b.body==null&&b.documentElement.appendChild(b.createElement("body"));b.body.style.overflow="auto";for(a=a.container.firstChild;a!=null;){g=a.cloneNode(true);b.body.appendChild(g);a=a.nextSibling}a=b.getElementsByTagName("g")[0];if(a!=null){a.setAttribute("transform","translate("+c+","+d+")");c=a.ownerSVGElement;c.setAttribute("width",e.width+Math.max(e.x,
-0)+3);c.setAttribute("height",e.height+Math.max(e.y,0)+3)}}mxUtils.removeCursors(b.body);return b},printScreen:function(a){var b=window.open();mxUtils.show(a,b.document);a=function(){b.focus();b.print();b.close()};mxClient.IS_GC?b.setTimeout(a,500):a()},popup:function(a,b){if(b){var c=document.createElement("div");c.style.overflow="scroll";c.style.width="636px";c.style.height="460px";var d=document.createElement("pre");d.innerHTML=mxUtils.htmlEntities(a,false).replace(/\n/g,"<br>").replace(/ /g,"&nbsp;");
-c.appendChild(d);c=new mxWindow("Popup Window",c,document.body.clientWidth/2-320,(document.body.clientHeight||document.documentElement.clientHeight)/2-240,640,480,false,true);c.setClosable(true);c.setVisible(true)}else if(mxClient.IS_NS){c=window.open();c.document.writeln("<pre>"+mxUtils.htmlEntities(a)+"</pre");c.document.close()}else{c=window.open();d=c.document.createElement("pre");d.innerHTML=mxUtils.htmlEntities(a,false).replace(/\n/g,"<br>").replace(/ /g,"&nbsp;");c.document.body.appendChild(d)}},
-alert:function(a){alert(a)},prompt:function(a,b){return prompt(a,b)},confirm:function(a){return confirm(a)},error:function(a,b,c,d){var e=document.createElement("div");e.style.padding="20px";var f=document.createElement("img");f.setAttribute("src",d||mxUtils.errorImage);f.setAttribute("valign","bottom");f.style.verticalAlign="middle";e.appendChild(f);e.appendChild(document.createTextNode("\u00a0"));e.appendChild(document.createTextNode("\u00a0"));e.appendChild(document.createTextNode("\u00a0"));mxUtils.write(e,
-a);var a=document.body.clientWidth,d=document.body.clientHeight||document.documentElement.clientHeight,g=new mxWindow(mxResources.get(mxUtils.errorResource)||mxUtils.errorResource,e,(a-b)/2,d/4,b,null,false,true);if(c){mxUtils.br(e);b=document.createElement("p");c=document.createElement("button");mxClient.IS_IE?c.style.cssText="float:right":c.setAttribute("style","float:right");mxEvent.addListener(c,"click",function(){g.destroy()});mxUtils.write(c,mxResources.get(mxUtils.closeResource)||mxUtils.closeResource);
-b.appendChild(c);e.appendChild(b);mxUtils.br(e);g.setClosable(true)}g.setVisible(true);return g},makeDraggable:function(a,b,c,d,e,f,g,h,k,i){a=new mxDragSource(a,c);a.dragOffset=new mxPoint(e!=null?e:0,f!=null?f:mxConstants.TOOLTIP_VERTICAL_OFFSET);a.autoscroll=g;a.setGuidesEnabled(false);if(k!=null)a.highlightDropTargets=k;if(i!=null)a.getDropTarget=i;a.getGraphForEvent=function(a){return typeof b=="function"?b(a):b};if(d!=null){a.createDragElement=function(){return d.cloneNode(true)};if(h)a.createPreviewElement=
-function(a){var b=d.cloneNode(true),c=parseInt(b.style.width),e=parseInt(b.style.height);b.style.width=Math.round(c*a.view.scale)+"px";b.style.height=Math.round(e*a.view.scale)+"px";return b}}return a}},mxConstants={DEFAULT_HOTSPOT:0.3,MIN_HOTSPOT_SIZE:8,MAX_HOTSPOT_SIZE:0,RENDERING_HINT_EXACT:"exact",RENDERING_HINT_FASTER:"faster",RENDERING_HINT_FASTEST:"fastest",DIALECT_SVG:"svg",DIALECT_VML:"vml",DIALECT_MIXEDHTML:"mixedHtml",DIALECT_PREFERHTML:"preferHtml",DIALECT_STRICTHTML:"strictHtml",NS_SVG:"http://www.w3.org/2000/svg",
-NS_XHTML:"http://www.w3.org/1999/xhtml",NS_XLINK:"http://www.w3.org/1999/xlink",SHADOWCOLOR:"gray",SHADOW_OFFSET_X:2,SHADOW_OFFSET_Y:3,SHADOW_OPACITY:1,NODETYPE_ELEMENT:1,NODETYPE_ATTRIBUTE:2,NODETYPE_TEXT:3,NODETYPE_CDATA:4,NODETYPE_ENTITY_REFERENCE:5,NODETYPE_ENTITY:6,NODETYPE_PROCESSING_INSTRUCTION:7,NODETYPE_COMMENT:8,NODETYPE_DOCUMENT:9,NODETYPE_DOCUMENTTYPE:10,NODETYPE_DOCUMENT_FRAGMENT:11,NODETYPE_NOTATION:12,TOOLTIP_VERTICAL_OFFSET:16,DEFAULT_VALID_COLOR:"#00FF00",DEFAULT_INVALID_COLOR:"#FF0000",
-HIGHLIGHT_STROKEWIDTH:3,CURSOR_MOVABLE_VERTEX:"move",CURSOR_MOVABLE_EDGE:"move",CURSOR_LABEL_HANDLE:"default",CURSOR_BEND_HANDLE:"pointer",CURSOR_CONNECT:"pointer",HIGHLIGHT_COLOR:"#00FF00",CONNECT_TARGET_COLOR:"#0000FF",INVALID_CONNECT_TARGET_COLOR:"#FF0000",DROP_TARGET_COLOR:"#0000FF",VALID_COLOR:"#00FF00",INVALID_COLOR:"#FF0000",EDGE_SELECTION_COLOR:"#00FF00",VERTEX_SELECTION_COLOR:"#00FF00",VERTEX_SELECTION_STROKEWIDTH:1,EDGE_SELECTION_STROKEWIDTH:1,VERTEX_SELECTION_DASHED:!0,EDGE_SELECTION_DASHED:!0,
-GUIDE_COLOR:"#FF0000",GUIDE_STROKEWIDTH:1,OUTLINE_COLOR:"#0099FF",OUTLINE_STROKEWIDTH:mxClient.IS_IE?2:3,HANDLE_SIZE:7,LABEL_HANDLE_SIZE:4,HANDLE_FILLCOLOR:"#00FF00",HANDLE_STROKECOLOR:"black",LABEL_HANDLE_FILLCOLOR:"yellow",CONNECT_HANDLE_FILLCOLOR:"#0000FF",LOCKED_HANDLE_FILLCOLOR:"#FF0000",OUTLINE_HANDLE_FILLCOLOR:"#00FFFF",OUTLINE_HANDLE_STROKECOLOR:"#0033FF",DEFAULT_FONTFAMILY:"Arial,Helvetica",DEFAULT_FONTSIZE:11,DEFAULT_STARTSIZE:40,DEFAULT_MARKERSIZE:6,DEFAULT_IMAGESIZE:24,ENTITY_SEGMENT:30,
-RECTANGLE_ROUNDING_FACTOR:0.15,LINE_ARCSIZE:20,ARROW_SPACING:10,ARROW_WIDTH:30,ARROW_SIZE:30,PAGE_FORMAT_A4_PORTRAIT:new mxRectangle(0,0,826,1169),PAGE_FORMAT_A4_LANDSCAPE:new mxRectangle(0,0,1169,826),PAGE_FORMAT_LETTER_PORTRAIT:new mxRectangle(0,0,850,1100),PAGE_FORMAT_LETTER_LANDSCAPE:new mxRectangle(0,0,1100,850),NONE:"none",STYLE_PERIMETER:"perimeter",STYLE_SOURCE_PORT:"sourcePort",STYLE_TARGET_PORT:"targetPort",STYLE_PORT_CONSTRAINT:"portConstraint",STYLE_OPACITY:"opacity",STYLE_TEXT_OPACITY:"textOpacity",
-STYLE_OVERFLOW:"overflow",STYLE_ORTHOGONAL:"orthogonal",STYLE_EXIT_X:"exitX",STYLE_EXIT_Y:"exitY",STYLE_EXIT_PERIMETER:"exitPerimeter",STYLE_ENTRY_X:"entryX",STYLE_ENTRY_Y:"entryY",STYLE_ENTRY_PERIMETER:"entryPerimeter",STYLE_WHITE_SPACE:"whiteSpace",STYLE_ROTATION:"rotation",STYLE_FILLCOLOR:"fillColor",STYLE_GRADIENTCOLOR:"gradientColor",STYLE_GRADIENT_DIRECTION:"gradientDirection",STYLE_STROKECOLOR:"strokeColor",STYLE_SEPARATORCOLOR:"separatorColor",STYLE_STROKEWIDTH:"strokeWidth",STYLE_ALIGN:"align",
-STYLE_VERTICAL_ALIGN:"verticalAlign",STYLE_LABEL_POSITION:"labelPosition",STYLE_VERTICAL_LABEL_POSITION:"verticalLabelPosition",STYLE_IMAGE_ASPECT:"imageAspect",STYLE_IMAGE_ALIGN:"imageAlign",STYLE_IMAGE_VERTICAL_ALIGN:"imageVerticalAlign",STYLE_GLASS:"glass",STYLE_IMAGE:"image",STYLE_IMAGE_WIDTH:"imageWidth",STYLE_IMAGE_HEIGHT:"imageHeight",STYLE_IMAGE_BACKGROUND:"imageBackground",STYLE_IMAGE_BORDER:"imageBorder",STYLE_IMAGE_FLIPH:"imageFlipH",STYLE_IMAGE_FLIPV:"imageFlipV",STYLE_STENCIL_FLIPH:"stencilFlipH",
-STYLE_STENCIL_FLIPV:"stencilFlipV",STYLE_NOLABEL:"noLabel",STYLE_NOEDGESTYLE:"noEdgeStyle",STYLE_LABEL_BACKGROUNDCOLOR:"labelBackgroundColor",STYLE_LABEL_BORDERCOLOR:"labelBorderColor",STYLE_LABEL_PADDING:"labelPadding",STYLE_INDICATOR_SHAPE:"indicatorShape",STYLE_INDICATOR_IMAGE:"indicatorImage",STYLE_INDICATOR_COLOR:"indicatorColor",STYLE_INDICATOR_STROKECOLOR:"indicatorStrokeColor",STYLE_INDICATOR_GRADIENTCOLOR:"indicatorGradientColor",STYLE_INDICATOR_SPACING:"indicatorSpacing",STYLE_INDICATOR_WIDTH:"indicatorWidth",
-STYLE_INDICATOR_HEIGHT:"indicatorHeight",STYLE_INDICATOR_DIRECTION:"indicatorDirection",STYLE_SHADOW:"shadow",STYLE_SEGMENT:"segment",STYLE_ENDARROW:"endArrow",STYLE_STARTARROW:"startArrow",STYLE_ENDSIZE:"endSize",STYLE_STARTSIZE:"startSize",STYLE_ENDFILL:"endFill",STYLE_STARTFILL:"startFill",STYLE_DASHED:"dashed",STYLE_DASH_PATTERN:"dashPattern",STYLE_ROUNDED:"rounded",STYLE_ARCSIZE:"arcSize",STYLE_SMOOTH:"smooth",STYLE_SOURCE_PERIMETER_SPACING:"sourcePerimeterSpacing",STYLE_TARGET_PERIMETER_SPACING:"targetPerimeterSpacing",
-STYLE_PERIMETER_SPACING:"perimeterSpacing",STYLE_SPACING:"spacing",STYLE_SPACING_TOP:"spacingTop",STYLE_SPACING_LEFT:"spacingLeft",STYLE_SPACING_BOTTOM:"spacingBottom",STYLE_SPACING_RIGHT:"spacingRight",STYLE_HORIZONTAL:"horizontal",STYLE_DIRECTION:"direction",STYLE_ELBOW:"elbow",STYLE_FONTCOLOR:"fontColor",STYLE_FONTFAMILY:"fontFamily",STYLE_FONTSIZE:"fontSize",STYLE_FONTSTYLE:"fontStyle",STYLE_AUTOSIZE:"autosize",STYLE_FOLDABLE:"foldable",STYLE_EDITABLE:"editable",STYLE_BENDABLE:"bendable",STYLE_MOVABLE:"movable",
-STYLE_RESIZABLE:"resizable",STYLE_CLONEABLE:"cloneable",STYLE_DELETABLE:"deletable",STYLE_SHAPE:"shape",STYLE_EDGE:"edgeStyle",STYLE_LOOP:"loopStyle",STYLE_ROUTING_CENTER_X:"routingCenterX",STYLE_ROUTING_CENTER_Y:"routingCenterY",FONT_BOLD:1,FONT_ITALIC:2,FONT_UNDERLINE:4,FONT_SHADOW:8,SHAPE_RECTANGLE:"rectangle",SHAPE_ELLIPSE:"ellipse",SHAPE_DOUBLE_ELLIPSE:"doubleEllipse",SHAPE_RHOMBUS:"rhombus",SHAPE_LINE:"line",SHAPE_IMAGE:"image",SHAPE_ARROW:"arrow",SHAPE_LABEL:"label",SHAPE_CYLINDER:"cylinder",
-SHAPE_SWIMLANE:"swimlane",SHAPE_CONNECTOR:"connector",SHAPE_ACTOR:"actor",SHAPE_CLOUD:"cloud",SHAPE_TRIANGLE:"triangle",SHAPE_HEXAGON:"hexagon",ARROW_CLASSIC:"classic",ARROW_BLOCK:"block",ARROW_OPEN:"open",ARROW_OVAL:"oval",ARROW_DIAMOND:"diamond",ARROW_DIAMOND_THIN:"diamondThin",ALIGN_LEFT:"left",ALIGN_CENTER:"center",ALIGN_RIGHT:"right",ALIGN_TOP:"top",ALIGN_MIDDLE:"middle",ALIGN_BOTTOM:"bottom",DIRECTION_NORTH:"north",DIRECTION_SOUTH:"south",DIRECTION_EAST:"east",DIRECTION_WEST:"west",DIRECTION_MASK_NONE:0,
-DIRECTION_MASK_WEST:1,DIRECTION_MASK_NORTH:2,DIRECTION_MASK_SOUTH:4,DIRECTION_MASK_EAST:8,DIRECTION_MASK_ALL:15,ELBOW_VERTICAL:"vertical",ELBOW_HORIZONTAL:"horizontal",EDGESTYLE_ELBOW:"elbowEdgeStyle",EDGESTYLE_ENTITY_RELATION:"entityRelationEdgeStyle",EDGESTYLE_LOOP:"loopEdgeStyle",EDGESTYLE_SIDETOSIDE:"sideToSideEdgeStyle",EDGESTYLE_TOPTOBOTTOM:"topToBottomEdgeStyle",EDGESTYLE_ORTHOGONAL:"orthogonalEdgeStyle",EDGESTYLE_SEGMENT:"segmentEdgeStyle",PERIMETER_ELLIPSE:"ellipsePerimeter",PERIMETER_RECTANGLE:"rectanglePerimeter",
-PERIMETER_RHOMBUS:"rhombusPerimeter",PERIMETER_TRIANGLE:"trianglePerimeter"};function mxEventObject(a){this.name=a;this.properties=[];for(var b=1;b<arguments.length;b=b+2)arguments[b+1]!=null&&(this.properties[arguments[b]]=arguments[b+1])}mxEventObject.prototype.name=null;mxEventObject.prototype.properties=null;mxEventObject.prototype.consumed=!1;mxEventObject.prototype.getName=function(){return this.name};mxEventObject.prototype.getProperties=function(){return this.properties};
-mxEventObject.prototype.getProperty=function(a){return this.properties[a]};mxEventObject.prototype.isConsumed=function(){return this.consumed};mxEventObject.prototype.consume=function(){this.consumed=true};function mxMouseEvent(a,b){this.evt=a;this.state=b}mxMouseEvent.prototype.consumed=!1;mxMouseEvent.prototype.evt=null;mxMouseEvent.prototype.graphX=null;mxMouseEvent.prototype.graphY=null;mxMouseEvent.prototype.state=null;mxMouseEvent.prototype.getEvent=function(){return this.evt};
-mxMouseEvent.prototype.getSource=function(){return mxEvent.getSource(this.evt)};mxMouseEvent.prototype.isSource=function(a){if(a!=null)for(var b=this.getSource();b!=null;){if(b==a.node)return true;b=b.parentNode}return false};mxMouseEvent.prototype.getX=function(){return mxEvent.getClientX(this.getEvent())};mxMouseEvent.prototype.getY=function(){return mxEvent.getClientY(this.getEvent())};mxMouseEvent.prototype.getGraphX=function(){return this.graphX};mxMouseEvent.prototype.getGraphY=function(){return this.graphY};
-mxMouseEvent.prototype.getState=function(){return this.state};mxMouseEvent.prototype.getCell=function(){var a=this.getState();return a!=null?a.cell:null};mxMouseEvent.prototype.isPopupTrigger=function(){return mxEvent.isPopupTrigger(this.getEvent())};mxMouseEvent.prototype.isConsumed=function(){return this.consumed};mxMouseEvent.prototype.consume=function(a){(a!=null?a:1)&&this.evt.preventDefault&&this.evt.preventDefault();this.evt.returnValue=false;this.consumed=true};
-function mxEventSource(a){this.setEventSource(a)}mxEventSource.prototype.eventListeners=null;mxEventSource.prototype.eventsEnabled=!0;mxEventSource.prototype.eventSource=null;mxEventSource.prototype.isEventsEnabled=function(){return this.eventsEnabled};mxEventSource.prototype.setEventsEnabled=function(a){this.eventsEnabled=a};mxEventSource.prototype.getEventSource=function(){return this.eventSource};mxEventSource.prototype.setEventSource=function(a){this.eventSource=a};
-mxEventSource.prototype.addListener=function(a,b){if(this.eventListeners==null)this.eventListeners=[];this.eventListeners.push(a);this.eventListeners.push(b)};mxEventSource.prototype.removeListener=function(a){if(this.eventListeners!=null)for(var b=0;b<this.eventListeners.length;)this.eventListeners[b+1]==a?this.eventListeners.splice(b,2):b=b+2};
-mxEventSource.prototype.fireEvent=function(a,b){if(this.eventListeners!=null&&this.isEventsEnabled()){a==null&&(a=new mxEventObject);b==null&&(b=this.getEventSource());b==null&&(b=this);for(var c=[b,a],d=0;d<this.eventListeners.length;d=d+2){var e=this.eventListeners[d];(e==null||e==a.getName())&&this.eventListeners[d+1].apply(this,c)}}};
-var mxEvent={objects:[],addListener:function(){var a=function(a,c,d){if(a.mxListenerList==null){a.mxListenerList=[];mxEvent.objects.push(a)}a.mxListenerList.push({name:c,f:d})};return window.addEventListener?function(b,c,d){b.addEventListener(c,d,false);a(b,c,d)}:function(b,c,d){b.attachEvent("on"+c,d);a(b,c,d)}}(),removeListener:function(){var a=function(a,c,d){if(a.mxListenerList!=null){for(var c=a.mxListenerList.length,e=0;e<c;e++)if(a.mxListenerList[e].f==d){a.mxListenerList.splice(e,1);break}if(a.mxListenerList.length==
-0)a.mxListenerList=null}};return window.removeEventListener?function(b,c,d){b.removeEventListener(c,d,false);a(b,c,d)}:function(b,c,d){b.detachEvent("on"+c,d);a(b,c,d)}}(),removeAllListeners:function(a){var b=a.mxListenerList;if(b!=null)for(;b.length>0;){var c=b[0];mxEvent.removeListener(a,c.name,c.f)}},redirectMouseEvents:function(a,b,c,d,e,f,g){var h=function(a){return typeof c=="function"?c(a):c},k=mxClient.IS_TOUCH?"touchmove":"mousemove",i=mxClient.IS_TOUCH?"touchend":"mouseup";mxEvent.addListener(a,
-mxClient.IS_TOUCH?"touchstart":"mousedown",function(a){d!=null?d(a):mxEvent.isConsumed(a)||b.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(a,h(a)))});mxEvent.addListener(a,k,function(a){e!=null?e(a):mxEvent.isConsumed(a)||b.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(a,h(a)))});mxEvent.addListener(a,i,function(a){f!=null?f(a):mxEvent.isConsumed(a)||b.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(a,h(a)))});mxEvent.addListener(a,"dblclick",function(a){if(g!=null)g(a);else if(!mxEvent.isConsumed(a)){var c=
-h(a);b.dblClick(a,c!=null?c.cell:null)}})},release:function(a){if(a!=null){mxEvent.removeAllListeners(a);a=a.childNodes;if(a!=null)for(var b=a.length,c=0;c<b;c=c+1)mxEvent.release(a[c])}},addMouseWheelListener:function(a){if(a!=null){var b=function(b){if(b==null)b=window.event;var d=0,d=mxClient.IS_NS&&!mxClient.IS_SF&&!mxClient.IS_GC?-b.detail/2:b.wheelDelta/120;d!=0&&a(b,d>0)};mxClient.IS_NS?mxEvent.addListener(window,mxClient.IS_SF||mxClient.IS_GC?"mousewheel":"DOMMouseScroll",b):mxEvent.addListener(document,
-"mousewheel",b)}},disableContextMenu:function(){return mxClient.IS_IE&&(typeof document.documentMode==="undefined"||document.documentMode<9)?function(a){mxEvent.addListener(a,"contextmenu",function(){return false})}:function(a){a.setAttribute("oncontextmenu","return false;")}}(),getSource:function(a){return a.srcElement!=null?a.srcElement:a.target},isConsumed:function(a){return a.isConsumed!=null&&a.isConsumed},isLeftMouseButton:function(a){return a.button==(mxClient.IS_IE&&(typeof document.documentMode===
-"undefined"||document.documentMode<9)?1:0)},isRightMouseButton:function(a){return a.button==2},isPopupTrigger:function(a){return mxEvent.isRightMouseButton(a)||mxEvent.isShiftDown(a)&&!mxEvent.isControlDown(a)},isShiftDown:function(a){return a!=null?a.shiftKey:false},isAltDown:function(a){return a!=null?a.altKey:false},isControlDown:function(a){return a!=null?a.ctrlKey:false},isMetaDown:function(a){return a!=null?a.metaKey:false},getMainEvent:function(a){(a.type=="touchstart"||a.type=="touchmove")&&
-a.touches!=null&&a.touches[0]!=null?a=a.touches[0]:a.type=="touchend"&&(a.changedTouches!=null&&a.changedTouches[0]!=null)&&(a=a.changedTouches[0]);return a},getClientX:function(a){return mxEvent.getMainEvent(a).clientX},getClientY:function(a){return mxEvent.getMainEvent(a).clientY},consume:function(a,b,c){c=c!=null?c:true;if(b!=null?b:1)if(a.preventDefault){c&&a.stopPropagation();a.preventDefault()}else if(c)a.cancelBubble=true;a.isConsumed=true;a.returnValue=false},LABEL_HANDLE:-1,ROTATION_HANDLE:-2,
-MOUSE_DOWN:"mouseDown",MOUSE_MOVE:"mouseMove",MOUSE_UP:"mouseUp",ACTIVATE:"activate",RESIZE_START:"resizeStart",RESIZE:"resize",RESIZE_END:"resizeEnd",MOVE_START:"moveStart",MOVE:"move",MOVE_END:"moveEnd",PAN_START:"panStart",PAN:"pan",PAN_END:"panEnd",MINIMIZE:"minimize",NORMALIZE:"normalize",MAXIMIZE:"maximize",HIDE:"hide",SHOW:"show",CLOSE:"close",DESTROY:"destroy",REFRESH:"refresh",SIZE:"size",SELECT:"select",FIRED:"fired",GET:"get",RECEIVE:"receive",CONNECT:"connect",DISCONNECT:"disconnect",
-SUSPEND:"suspend",RESUME:"resume",MARK:"mark",SESSION:"session",ROOT:"root",POST:"post",OPEN:"open",SAVE:"save",BEFORE_ADD_VERTEX:"beforeAddVertex",ADD_VERTEX:"addVertex",AFTER_ADD_VERTEX:"afterAddVertex",DONE:"done",EXECUTE:"execute",BEGIN_UPDATE:"beginUpdate",END_UPDATE:"endUpdate",BEFORE_UNDO:"beforeUndo",UNDO:"undo",REDO:"redo",CHANGE:"change",NOTIFY:"notify",LAYOUT_CELLS:"layoutCells",CLICK:"click",SCALE:"scale",TRANSLATE:"translate",SCALE_AND_TRANSLATE:"scaleAndTranslate",UP:"up",DOWN:"down",
-ADD:"add",REMOVE:"remove",CLEAR:"clear",ADD_CELLS:"addCells",CELLS_ADDED:"cellsAdded",MOVE_CELLS:"moveCells",CELLS_MOVED:"cellsMoved",RESIZE_CELLS:"resizeCells",CELLS_RESIZED:"cellsResized",TOGGLE_CELLS:"toggleCells",CELLS_TOGGLED:"cellsToggled",ORDER_CELLS:"orderCells",CELLS_ORDERED:"cellsOrdered",REMOVE_CELLS:"removeCells",CELLS_REMOVED:"cellsRemoved",GROUP_CELLS:"groupCells",UNGROUP_CELLS:"ungroupCells",REMOVE_CELLS_FROM_PARENT:"removeCellsFromParent",FOLD_CELLS:"foldCells",CELLS_FOLDED:"cellsFolded",
-ALIGN_CELLS:"alignCells",LABEL_CHANGED:"labelChanged",CONNECT_CELL:"connectCell",CELL_CONNECTED:"cellConnected",SPLIT_EDGE:"splitEdge",FLIP_EDGE:"flipEdge",START_EDITING:"startEditing",ADD_OVERLAY:"addOverlay",REMOVE_OVERLAY:"removeOverlay",UPDATE_CELL_SIZE:"updateCellSize",ESCAPE:"escape",CLICK:"click",DOUBLE_CLICK:"doubleClick",START:"start",RESET:"reset"};
-function mxXmlRequest(a,b,c,d,e,f){this.url=a;this.params=b;this.method=c||"POST";this.async=d!=null?d:true;this.username=e;this.password=f}mxXmlRequest.prototype.url=null;mxXmlRequest.prototype.params=null;mxXmlRequest.prototype.method=null;mxXmlRequest.prototype.async=null;mxXmlRequest.prototype.binary=!1;mxXmlRequest.prototype.username=null;mxXmlRequest.prototype.password=null;mxXmlRequest.prototype.request=null;mxXmlRequest.prototype.isBinary=function(){return this.binary};
-mxXmlRequest.prototype.setBinary=function(a){this.binary=a};mxXmlRequest.prototype.getText=function(){return this.request.responseText};mxXmlRequest.prototype.isReady=function(){return this.request.readyState==4};mxXmlRequest.prototype.getDocumentElement=function(){var a=this.getXml();return a!=null?a.documentElement:null};
-mxXmlRequest.prototype.getXml=function(){var a=this.request.responseXML;if(document.documentMode>=9||a==null||a.documentElement==null)a=mxUtils.parseXml(this.request.responseText);return a};mxXmlRequest.prototype.getText=function(){return this.request.responseText};mxXmlRequest.prototype.getStatus=function(){return this.request.status};
-mxXmlRequest.prototype.create=function(){if(window.XMLHttpRequest)return function(){var a=new XMLHttpRequest;this.isBinary()&&a.overrideMimeType&&a.overrideMimeType("text/plain; charset=x-user-defined");return a};if(typeof ActiveXObject!="undefined")return function(){return new ActiveXObject("Microsoft.XMLHTTP")}}();
-mxXmlRequest.prototype.send=function(a){this.request=this.create();if(this.request!=null){if(a!=null)this.request.onreadystatechange=mxUtils.bind(this,function(){if(this.isReady()){a(this);this.onreadystatechaange=null}});this.request.open(this.method,this.url,this.async,this.username,this.password);this.setRequestHeaders(this.request,this.params);this.request.send(this.params)}};mxXmlRequest.prototype.setRequestHeaders=function(a,b){b!=null&&a.setRequestHeader("Content-Type","application/x-www-form-urlencoded")};
-mxXmlRequest.prototype.simulate=function(a,b){var a=a||document,c=null;if(a==document){c=window.onbeforeunload;window.onbeforeunload=null}var d=a.createElement("form");d.setAttribute("method",this.method);d.setAttribute("action",this.url);b!=null&&d.setAttribute("target",b);d.style.display="none";d.style.visibility="hidden";for(var e=this.params.indexOf("&")>0?this.params.split("&"):this.params.split(),f=0;f<e.length;f++){var g=e[f].indexOf("=");if(g>0){var h=e[f].substring(0,g),k=e[f].substring(g+
-1),g=a.createElement("textarea");g.setAttribute("name",h);k=k.replace(/\n/g,"&#xa;");h=a.createTextNode(k);g.appendChild(h);d.appendChild(g)}}a.body.appendChild(d);d.submit();a.body.removeChild(d);if(c!=null)window.onbeforeunload=c};
-var mxClipboard={STEPSIZE:10,insertCount:1,cells:null,isEmpty:function(){return mxClipboard.cells==null},cut:function(a,b){b=mxClipboard.copy(a,b);mxClipboard.insertCount=0;mxClipboard.removeCells(a,b);return b},removeCells:function(a,b){a.removeCells(b)},copy:function(a,b){var b=b||a.getSelectionCells(),c=a.getExportableCells(b);mxClipboard.insertCount=1;mxClipboard.cells=a.cloneCells(c);return c},paste:function(a){if(mxClipboard.cells!=null){var b=a.getImportableCells(mxClipboard.cells),c=mxClipboard.insertCount*
-mxClipboard.STEPSIZE,d=a.getDefaultParent(),b=a.importCells(b,c,c,d);mxClipboard.insertCount++;a.setSelectionCells(b)}}};function mxWindow(a,b,c,d,e,f,g,h,k,i){if(b!=null){g=g!=null?g:true;this.content=b;this.init(c,d,e,f,i);this.installMaximizeHandler();this.installMinimizeHandler();this.installCloseHandler();this.setMinimizable(g);this.setTitle(a);(h==null||h)&&this.installMoveHandler();k!=null&&k.parentNode!=null?k.parentNode.replaceChild(this.div,k):document.body.appendChild(this.div)}}
-mxWindow.prototype=new mxEventSource;mxWindow.prototype.constructor=mxWindow;mxWindow.prototype.closeImage=mxClient.imageBasePath+"/close.gif";mxWindow.prototype.minimizeImage=mxClient.imageBasePath+"/minimize.gif";mxWindow.prototype.normalizeImage=mxClient.imageBasePath+"/normalize.gif";mxWindow.prototype.maximizeImage=mxClient.imageBasePath+"/maximize.gif";mxWindow.prototype.resizeImage=mxClient.imageBasePath+"/resize.gif";mxWindow.prototype.visible=!1;mxWindow.prototype.content=!1;
-mxWindow.prototype.minimumSize=new mxRectangle(0,0,50,40);mxWindow.prototype.title=!1;mxWindow.prototype.content=!1;mxWindow.prototype.destroyOnClose=!0;
-mxWindow.prototype.init=function(a,b,c,d,e){e=e!=null?e:"mxWindow";this.div=document.createElement("div");this.div.className=e;this.div.style.left=a+"px";this.div.style.top=b+"px";this.table=document.createElement("table");this.table.className=e;if(c!=null){if(!mxClient.IS_IE)this.div.style.width=c+"px";this.table.style.width=c+"px"}if(d!=null){if(!mxClient.IS_IE)this.div.style.height=d+"px";this.table.style.height=d+"px"}a=document.createElement("tbody");b=document.createElement("tr");this.title=
-document.createElement("td");this.title.className=e+"Title";b.appendChild(this.title);a.appendChild(b);b=document.createElement("tr");this.td=document.createElement("td");this.td.className=e+"Pane";this.contentWrapper=document.createElement("div");this.contentWrapper.className=e+"Pane";this.contentWrapper.style.width="100%";this.contentWrapper.appendChild(this.content);if(mxClient.IS_IE||this.content.nodeName.toUpperCase()!="DIV")this.contentWrapper.style.height="100%";this.td.appendChild(this.contentWrapper);
-b.appendChild(this.td);a.appendChild(b);this.table.appendChild(a);this.div.appendChild(this.table);e=mxUtils.bind(this,function(){this.activate()});a=mxClient.IS_TOUCH?"touchstart":"mousedown";mxEvent.addListener(this.title,a,e);mxEvent.addListener(this.table,a,e);this.hide()};mxWindow.prototype.setTitle=function(a){for(var b=this.title.firstChild;b!=null;){var c=b.nextSibling;b.nodeType==mxConstants.NODETYPE_TEXT&&b.parentNode.removeChild(b);b=c}mxUtils.write(this.title,a||"")};
-mxWindow.prototype.setScrollable=function(a){if(navigator.userAgent.indexOf("Presto/2.5")<0)this.contentWrapper.style.overflow=a?"auto":"hidden"};
-mxWindow.prototype.activate=function(){if(mxWindow.activeWindow!=this){var a=mxUtils.getCurrentStyle(this.getElement()),a=a!=null?a.zIndex:3;if(mxWindow.activeWindow){var b=mxWindow.activeWindow.getElement();if(b!=null&&b.style!=null)b.style.zIndex=a}b=mxWindow.activeWindow;this.getElement().style.zIndex=parseInt(a)+1;mxWindow.activeWindow=this;this.fireEvent(new mxEventObject(mxEvent.ACTIVATE,"previousWindow",b))}};mxWindow.prototype.getElement=function(){return this.div};
-mxWindow.prototype.fit=function(){mxUtils.fit(this.div)};mxWindow.prototype.isResizable=function(){return this.resize!=null?this.resize.style.display!="none":false};
-mxWindow.prototype.setResizable=function(a){if(a)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",mxClient.imageBasePath+"/resize.gif");this.resize.style.cursor="nw-resize";var b=mxClient.IS_TOUCH?"touchmove":"mousemove",c=mxClient.IS_TOUCH?"touchend":"mouseup";mxEvent.addListener(this.resize,mxClient.IS_TOUCH?"touchstart":"mousedown",mxUtils.bind(this,function(a){this.activate();
-var e=mxEvent.getClientX(a),f=mxEvent.getClientY(a),g=this.div.offsetWidth,h=this.div.offsetHeight,k=mxUtils.bind(this,function(a){var b=mxEvent.getClientX(a)-e,c=mxEvent.getClientY(a)-f;this.setSize(g+b,h+c);this.fireEvent(new mxEventObject(mxEvent.RESIZE,"event",a));mxEvent.consume(a)}),i=mxUtils.bind(this,function(a){mxEvent.removeListener(document,b,k);mxEvent.removeListener(document,c,i);this.fireEvent(new mxEventObject(mxEvent.RESIZE_END,"event",a));mxEvent.consume(a)});mxEvent.addListener(document,
-b,k);mxEvent.addListener(document,c,i);this.fireEvent(new mxEventObject(mxEvent.RESIZE_START,"event",a));mxEvent.consume(a)}));this.div.appendChild(this.resize)}else this.resize.style.display="inline";else if(this.resize!=null)this.resize.style.display="none"};
-mxWindow.prototype.setSize=function(a,b){a=Math.max(this.minimumSize.width,a);b=Math.max(this.minimumSize.height,b);if(!mxClient.IS_IE){this.div.style.width=a+"px";this.div.style.height=b+"px"}this.table.style.width=a+"px";this.table.style.height=b+"px";if(!mxClient.IS_IE)this.contentWrapper.style.height=this.div.offsetHeight-this.title.offsetHeight-2+"px"};mxWindow.prototype.setMinimizable=function(a){this.minimize.style.display=a?"":"none"};
-mxWindow.prototype.getMinimumSize=function(){return new mxRectangle(0,0,0,this.title.offsetHeight)};
-mxWindow.prototype.installMinimizeHandler=function(){this.minimize=document.createElement("img");this.minimize.setAttribute("src",this.minimizeImage);this.minimize.setAttribute("align","right");this.minimize.setAttribute("title","Minimize");this.minimize.style.cursor="pointer";this.minimize.style.marginRight="1px";this.minimize.style.display="none";this.title.appendChild(this.minimize);var a=false,b=null,c=null,d=mxUtils.bind(this,function(d){this.activate();if(a){a=false;this.minimize.setAttribute("src",
-this.minimizeImage);this.minimize.setAttribute("title","Minimize");this.contentWrapper.style.display="";this.maximize.style.display=b;if(!mxClient.IS_IE)this.div.style.height=c;this.table.style.height=c;if(this.resize!=null)this.resize.style.visibility="";this.fireEvent(new mxEventObject(mxEvent.NORMALIZE,"event",d))}else{a=true;this.minimize.setAttribute("src",this.normalizeImage);this.minimize.setAttribute("title","Normalize");this.contentWrapper.style.display="none";b=this.maximize.style.display;
-this.maximize.style.display="none";c=this.table.style.height;var f=this.getMinimumSize();if(f.height>0){if(!mxClient.IS_IE)this.div.style.height=f.height+"px";this.table.style.height=f.height+"px"}if(f.width>0){if(!mxClient.IS_IE)this.div.style.width=f.width+"px";this.table.style.width=f.width+"px"}if(this.resize!=null)this.resize.style.visibility="hidden";this.fireEvent(new mxEventObject(mxEvent.MINIMIZE,"event",d))}mxEvent.consume(d)});mxEvent.addListener(this.minimize,mxClient.IS_TOUCH?"touchstart":
-"mousedown",d)};mxWindow.prototype.setMaximizable=function(a){this.maximize.style.display=a?"":"none"};
-mxWindow.prototype.installMaximizeHandler=function(){this.maximize=document.createElement("img");this.maximize.setAttribute("src",this.maximizeImage);this.maximize.setAttribute("align","right");this.maximize.setAttribute("title","Maximize");this.maximize.style.cursor="default";this.maximize.style.marginLeft="1px";this.maximize.style.cursor="pointer";this.maximize.style.display="none";this.title.appendChild(this.maximize);var a=false,b=null,c=null,d=null,e=null,f=mxUtils.bind(this,function(f){this.activate();
-if(this.maximize.style.display!="none"){if(a){a=false;this.maximize.setAttribute("src",this.maximizeImage);this.maximize.setAttribute("title","Maximize");this.contentWrapper.style.display="";this.minimize.style.visibility="";this.div.style.left=b+"px";this.div.style.top=c+"px";if(!mxClient.IS_IE){this.div.style.height=d;this.div.style.width=e;h=mxUtils.getCurrentStyle(this.contentWrapper);if(h.overflow=="auto"||this.resize!=null)this.contentWrapper.style.height=this.div.offsetHeight-this.title.offsetHeight-
-2+"px"}this.table.style.height=d;this.table.style.width=e;if(this.resize!=null)this.resize.style.visibility="";this.fireEvent(new mxEventObject(mxEvent.NORMALIZE,"event",f))}else{a=true;this.maximize.setAttribute("src",this.normalizeImage);this.maximize.setAttribute("title","Normalize");this.contentWrapper.style.display="";this.minimize.style.visibility="hidden";b=parseInt(this.div.style.left);c=parseInt(this.div.style.top);d=this.table.style.height;e=this.table.style.width;this.div.style.left="0px";
-this.div.style.top="0px";if(!mxClient.IS_IE){this.div.style.height=document.body.clientHeight-2+"px";this.div.style.width=document.body.clientWidth-2+"px"}this.table.style.width=document.body.clientWidth-2+"px";this.table.style.height=document.body.clientHeight-2+"px";if(this.resize!=null)this.resize.style.visibility="hidden";if(!mxClient.IS_IE){var h=mxUtils.getCurrentStyle(this.contentWrapper);if(h.overflow=="auto"||this.resize!=null)this.contentWrapper.style.height=this.div.offsetHeight-this.title.offsetHeight-
-2+"px"}this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE,"event",f))}mxEvent.consume(f)}});mxEvent.addListener(this.maximize,mxClient.IS_TOUCH?"touchstart":"mousedown",f);mxEvent.addListener(this.title,"dblclick",f)};
-mxWindow.prototype.installMoveHandler=function(){this.title.style.cursor="move";var a=mxClient.IS_TOUCH?"touchmove":"mousemove",b=mxClient.IS_TOUCH?"touchend":"mouseup";mxEvent.addListener(this.title,mxClient.IS_TOUCH?"touchstart":"mousedown",mxUtils.bind(this,function(c){var d=mxEvent.getClientX(c),e=mxEvent.getClientY(c),f=this.getX(),g=this.getY(),h=mxUtils.bind(this,function(a){var b=mxEvent.getClientX(a)-d,c=mxEvent.getClientY(a)-e;this.setLocation(f+b,g+c);this.fireEvent(new mxEventObject(mxEvent.MOVE,
-"event",a));mxEvent.consume(a)}),k=mxUtils.bind(this,function(c){mxEvent.removeListener(document,a,h);mxEvent.removeListener(document,b,k);this.fireEvent(new mxEventObject(mxEvent.MOVE_END,"event",c));mxEvent.consume(c)});mxEvent.addListener(document,a,h);mxEvent.addListener(document,b,k);this.fireEvent(new mxEventObject(mxEvent.MOVE_START,"event",c));mxEvent.consume(c)}))};mxWindow.prototype.setLocation=function(a,b){this.div.style.left=a+"px";this.div.style.top=b+"px"};mxWindow.prototype.getX=function(){return parseInt(this.div.style.left)};
-mxWindow.prototype.getY=function(){return parseInt(this.div.style.top)};
-mxWindow.prototype.installCloseHandler=function(){this.closeImg=document.createElement("img");this.closeImg.setAttribute("src",this.closeImage);this.closeImg.setAttribute("align","right");this.closeImg.setAttribute("title","Close");this.closeImg.style.marginLeft="2px";this.closeImg.style.cursor="pointer";this.closeImg.style.display="none";this.title.insertBefore(this.closeImg,this.title.firstChild);mxEvent.addListener(this.closeImg,mxClient.IS_TOUCH?"touchstart":"mousedown",mxUtils.bind(this,function(a){this.fireEvent(new mxEventObject(mxEvent.CLOSE,
-"event",a));this.destroyOnClose?this.destroy():this.setVisible(false);mxEvent.consume(a)}))};mxWindow.prototype.setImage=function(a){this.image=document.createElement("img");this.image.setAttribute("src",a);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)};mxWindow.prototype.setClosable=function(a){this.closeImg.style.display=a?"":"none"};
-mxWindow.prototype.isVisible=function(){return this.div!=null?this.div.style.visibility!="hidden":false};mxWindow.prototype.setVisible=function(a){this.div!=null&&this.isVisible()!=a&&(a?this.show():this.hide())};mxWindow.prototype.show=function(){this.div.style.visibility="";this.activate();var a=mxUtils.getCurrentStyle(this.contentWrapper);if(!mxClient.IS_IE&&(a.overflow=="auto"||this.resize!=null))this.contentWrapper.style.height=this.div.offsetHeight-this.title.offsetHeight-2+"px";this.fireEvent(new mxEventObject(mxEvent.SHOW))};
-mxWindow.prototype.hide=function(){this.div.style.visibility="hidden";this.fireEvent(new mxEventObject(mxEvent.HIDE))};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.contentWrapper=this.content=this.title=null};
-function mxForm(a){this.table=document.createElement("table");this.table.className=a;this.body=document.createElement("tbody");this.table.appendChild(this.body)}mxForm.prototype.table=null;mxForm.prototype.body=!1;mxForm.prototype.getTable=function(){return this.table};
-mxForm.prototype.addButtons=function(a,b){var c=document.createElement("tr"),d=document.createElement("td");c.appendChild(d);var d=document.createElement("td"),e=document.createElement("button");mxUtils.write(e,mxResources.get("ok")||"OK");d.appendChild(e);mxEvent.addListener(e,"click",function(){a()});e=document.createElement("button");mxUtils.write(e,mxResources.get("cancel")||"Cancel");d.appendChild(e);mxEvent.addListener(e,"click",function(){b()});c.appendChild(d);this.body.appendChild(c)};
-mxForm.prototype.addText=function(a,b){var c=document.createElement("input");c.setAttribute("type","text");c.value=b;return this.addField(a,c)};mxForm.prototype.addCheckbox=function(a,b){var c=document.createElement("input");c.setAttribute("type","checkbox");this.addField(a,c);if(b)c.checked=true;return c};mxForm.prototype.addTextarea=function(a,b,c){var d=document.createElement("textarea");mxClient.IS_NS&&c--;d.setAttribute("rows",c||2);d.value=b;return this.addField(a,d)};
-mxForm.prototype.addCombo=function(a,b,c){var d=document.createElement("select");c!=null&&d.setAttribute("size",c);b&&d.setAttribute("multiple","true");return this.addField(a,d)};mxForm.prototype.addOption=function(a,b,c,d){var e=document.createElement("option");mxUtils.writeln(e,b);e.setAttribute("value",c);d&&e.setAttribute("selected",d);a.appendChild(e)};
-mxForm.prototype.addField=function(a,b){var c=document.createElement("tr"),d=document.createElement("td");mxUtils.write(d,a);c.appendChild(d);d=document.createElement("td");d.appendChild(b);c.appendChild(d);this.body.appendChild(c);return b};function mxImage(a,b,c){this.src=a;this.width=b;this.height=c}mxImage.prototype.src=null;mxImage.prototype.width=null;mxImage.prototype.height=null;
-function mxDivResizer(a,b){if(a.nodeName.toLowerCase()=="div"){b==null&&(b=window);this.div=a;var c=mxUtils.getCurrentStyle(a);if(c!=null){this.resizeWidth=c.width=="auto";this.resizeHeight=c.height=="auto"}mxEvent.addListener(b,"resize",mxUtils.bind(this,function(){if(!this.handlingResize){this.handlingResize=true;this.resize();this.handlingResize=false}}));this.resize()}}mxDivResizer.prototype.resizeWidth=!0;mxDivResizer.prototype.resizeHeight=!0;mxDivResizer.prototype.handlingResize=!1;
-mxDivResizer.prototype.resize=function(){var a=this.getDocumentWidth(),b=this.getDocumentHeight(),c=parseInt(this.div.style.left),d=parseInt(this.div.style.right),e=parseInt(this.div.style.top),f=parseInt(this.div.style.bottom);if(this.resizeWidth&&!isNaN(c)&&!isNaN(d)&&c>=0&&d>=0&&a-d-c>0)this.div.style.width=a-d-c+"px";if(this.resizeHeight&&!isNaN(e)&&!isNaN(f)&&e>=0&&f>=0&&b-e-f>0)this.div.style.height=b-e-f+"px"};mxDivResizer.prototype.getDocumentWidth=function(){return document.body.clientWidth};
-mxDivResizer.prototype.getDocumentHeight=function(){return document.body.clientHeight};function mxDragSource(a,b){this.element=a;this.dropHandler=b;mxEvent.addListener(a,mxClient.IS_TOUCH?"touchstart":"mousedown",mxUtils.bind(this,this.mouseDown))}mxDragSource.prototype.element=null;mxDragSource.prototype.dropHandler=null;mxDragSource.prototype.dragOffset=null;mxDragSource.prototype.dragElement=null;mxDragSource.prototype.previewElement=null;mxDragSource.prototype.enabled=!0;
-mxDragSource.prototype.currentGraph=null;mxDragSource.prototype.currentDropTarget=null;mxDragSource.prototype.currentPoint=null;mxDragSource.prototype.currentGuide=null;mxDragSource.prototype.currentHighlight=null;mxDragSource.prototype.autoscroll=!0;mxDragSource.prototype.guidesEnabled=!0;mxDragSource.prototype.gridEnabled=!0;mxDragSource.prototype.highlightDropTargets=!0;mxDragSource.prototype.dragElementZIndex=100;mxDragSource.prototype.dragElementOpacity=70;mxDragSource.prototype.isEnabled=function(){return this.enabled};
-mxDragSource.prototype.setEnabled=function(a){this.enabled=a};mxDragSource.prototype.isGuidesEnabled=function(){return this.guidesEnabled};mxDragSource.prototype.setGuidesEnabled=function(a){this.guidesEnabled=a};mxDragSource.prototype.isGridEnabled=function(){return this.gridEnabled};mxDragSource.prototype.setGridEnabled=function(a){this.gridEnabled=a};mxDragSource.prototype.getGraphForEvent=function(){return null};mxDragSource.prototype.getDropTarget=function(a,b,c){return a.getCellAt(b,c)};
-mxDragSource.prototype.createDragElement=function(){return this.element.cloneNode(true)};mxDragSource.prototype.createPreviewElement=function(){return null};
-mxDragSource.prototype.mouseDown=function(a){if(this.enabled&&!mxEvent.isConsumed(a)&&this.mouseMoveHandler==null){this.startDrag(a);var b=mxClient.IS_TOUCH?"touchmove":"mousemove",c=mxClient.IS_TOUCH?"touchend":"mouseup";this.mouseMoveHandler=mxUtils.bind(this,this.mouseMove);mxEvent.addListener(document,b,this.mouseMoveHandler);this.mouseUpHandler=mxUtils.bind(this,this.mouseUp);mxEvent.addListener(document,c,this.mouseUpHandler);mxEvent.consume(a,true,false)}};
-mxDragSource.prototype.startDrag=function(a){this.dragElement=this.createDragElement(a);this.dragElement.style.position="absolute";this.dragElement.style.zIndex=this.dragElementZIndex;mxUtils.setOpacity(this.dragElement,this.dragElementOpacity)};mxDragSource.prototype.stopDrag=function(){if(this.dragElement!=null){this.dragElement.parentNode!=null&&this.dragElement.parentNode.removeChild(this.dragElement);this.dragElement=null}};
-mxDragSource.prototype.graphContainsEvent=function(a,b){var c=mxEvent.getClientX(b),d=mxEvent.getClientY(b),e=mxUtils.getOffset(a.container),f=mxUtils.getScrollOrigin();return c>=e.x-f.x&&d>=e.y-f.y&&c<=e.x-f.x+a.container.offsetWidth&&d<=e.y-f.y+a.container.offsetHeight};
-mxDragSource.prototype.mouseMove=function(a){var b=this.getGraphForEvent(a);b!=null&&!this.graphContainsEvent(b,a)&&(b=null);if(b!=this.currentGraph){this.currentGraph!=null&&this.dragExit(this.currentGraph);this.currentGraph=b;this.currentGraph!=null&&this.dragEnter(this.currentGraph)}this.currentGraph!=null&&this.dragOver(this.currentGraph,a);if(this.dragElement!=null&&(this.previewElement==null||this.previewElement.style.visibility!="visible")){var b=mxEvent.getClientX(a),c=mxEvent.getClientY(a);
-this.dragElement.parentNode==null&&document.body.appendChild(this.dragElement);this.dragElement.style.visibility="visible";if(this.dragOffset!=null){b=b+this.dragOffset.x;c=c+this.dragOffset.y}b=b+(document.body.scrollLeft||document.documentElement.scrollLeft);c=c+(document.body.scrollTop||document.documentElement.scrollTop);this.dragElement.style.left=b+"px";this.dragElement.style.top=c+"px"}else if(this.dragElement!=null)this.dragElement.style.visibility="hidden";mxEvent.consume(a)};
-mxDragSource.prototype.mouseUp=function(a){if(this.currentGraph!=null){if(this.currentPoint!=null&&(this.previewElement==null||this.previewElement.style.visibility!="hidden")){var b=this.currentGraph.view.scale,c=this.currentGraph.view.translate;this.drop(this.currentGraph,a,this.currentDropTarget,this.currentPoint.x/b-c.x,this.currentPoint.y/b-c.y)}this.dragExit(this.currentGraph)}this.stopDrag(a);this.currentGraph=null;if(this.mouseMoveHandler!=null){mxEvent.removeListener(document,mxClient.IS_TOUCH?
-"touchmove":"mousemove",this.mouseMoveHandler);this.mouseMoveHandler=null}if(this.mouseUpHandler!=null){mxEvent.removeListener(document,mxClient.IS_TOUCH?"touchend":"mouseup",this.mouseUpHandler);this.mouseUpHandler=null}mxEvent.consume(a)};
-mxDragSource.prototype.dragEnter=function(a){a.isMouseDown=true;this.previewElement=this.createPreviewElement(a);if(this.isGuidesEnabled()&&this.previewElement!=null)this.currentGuide=new mxGuide(a,a.graphHandler.getGuideStates());if(this.highlightDropTargets)this.currentHighlight=new mxCellHighlight(a,mxConstants.DROP_TARGET_COLOR)};
-mxDragSource.prototype.dragExit=function(a){this.currentPoint=this.currentDropTarget=null;a.isMouseDown=false;if(this.previewElement!=null){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}};
-mxDragSource.prototype.dragOver=function(a,b){var c=mxUtils.getOffset(a.container),d=mxUtils.getScrollOrigin(a.container),e=mxEvent.getClientX(b)-c.x+d.x,c=mxEvent.getClientY(b)-c.y+d.y;a.autoScroll&&(this.autoscroll==null||this.autoscroll)&&a.scrollPointToVisible(e,c,a.autoExtend);if(this.currentHighlight!=null&&a.isDropEnabled()){this.currentDropTarget=this.getDropTarget(a,e,c);this.currentHighlight.highlight(a.getView().getState(this.currentDropTarget))}if(this.previewElement!=null){if(this.previewElement.parentNode==
-null){a.container.appendChild(this.previewElement);this.previewElement.style.zIndex="3";this.previewElement.style.position="absolute"}var d=this.isGridEnabled()&&a.isGridEnabledEvent(b),f=true;if(this.currentGuide!=null&&this.currentGuide.isEnabledForEvent(b))var f=parseInt(this.previewElement.style.width),g=parseInt(this.previewElement.style.height),f=new mxRectangle(0,0,f,g),c=new mxPoint(e,c),c=this.currentGuide.move(f,c,d),f=false,e=c.x,c=c.y;else if(d)var d=a.view.scale,g=a.view.translate,h=
-a.gridSize/2,e=(a.snap(e/d-g.x-h)+g.x)*d,c=(a.snap(c/d-g.y-h)+g.y)*d;this.currentGuide!=null&&f&&this.currentGuide.hide();if(this.previewOffset!=null){e=e+this.previewOffset.x;c=c+this.previewOffset.y}this.previewElement.style.left=Math.round(e)+"px";this.previewElement.style.top=Math.round(c)+"px";this.previewElement.style.visibility="visible"}this.currentPoint=new mxPoint(e,c)};mxDragSource.prototype.drop=function(a,b,c,d,e){this.dropHandler(a,b,c,d,e);a.container.focus()};
-function mxToolbar(a){this.container=a}mxToolbar.prototype=new mxEventSource;mxToolbar.prototype.constructor=mxToolbar;mxToolbar.prototype.container=null;mxToolbar.prototype.enabled=!0;mxToolbar.prototype.noReset=!1;mxToolbar.prototype.updateDefaultMode=!0;
-mxToolbar.prototype.addItem=function(a,b,c,d,e,f){var g=document.createElement(b!=null?"img":"button"),h=e||(f!=null?"mxToolbarMode":"mxToolbarItem");g.className=h;g.setAttribute("src",b);a!=null&&(b!=null?g.setAttribute("title",a):mxUtils.write(g,a));this.container.appendChild(g);c!=null&&mxEvent.addListener(g,mxClient.IS_TOUCH?"touchend":"click",c);a=mxClient.IS_TOUCH?"touchend":"mouseup";mxEvent.addListener(g,mxClient.IS_TOUCH?"touchstart":"mousedown",mxUtils.bind(this,function(a){d!=null?g.setAttribute("src",
-d):g.style.backgroundColor="gray";if(f!=null){if(this.menu==null){this.menu=new mxPopupMenu;this.menu.init()}var b=this.currentImg;this.menu.isMenuShowing()&&this.menu.hideMenu();if(b!=g){this.currentImg=g;this.menu.factoryMethod=f;b=new mxPoint(g.offsetLeft,g.offsetTop+g.offsetHeight);this.menu.popup(b.x,b.y,null,a);if(this.menu.isMenuShowing()){g.className=h+"Selected";this.menu.hideMenu=function(){mxPopupMenu.prototype.hideMenu.apply(this);g.className=h;this.currentImg=null}}}}}));c=mxUtils.bind(this,
-function(){d!=null?g.setAttribute("src",b):g.style.backgroundColor=""});mxEvent.addListener(g,a,c);mxEvent.addListener(g,"mouseout",c);return g};mxToolbar.prototype.addCombo=function(a){var b=document.createElement("div");b.style.display="inline";b.className="mxToolbarComboContainer";var c=document.createElement("select");c.className=a||"mxToolbarCombo";b.appendChild(c);this.container.appendChild(b);return c};
-mxToolbar.prototype.addActionCombo=function(a,b){var c=document.createElement("select");c.className=b||"mxToolbarCombo";this.addOption(c,a,null);mxEvent.addListener(c,"change",function(a){var b=c.options[c.selectedIndex];c.selectedIndex=0;b.funct!=null&&b.funct(a)});this.container.appendChild(c);return c};mxToolbar.prototype.addOption=function(a,b,c){var d=document.createElement("option");mxUtils.writeln(d,b);typeof c=="function"?d.funct=c:d.setAttribute("value",c);a.appendChild(d);return d};
-mxToolbar.prototype.addSwitchMode=function(a,b,c,d,e){var f=document.createElement("img");f.initialClassName=e||"mxToolbarMode";f.className=f.initialClassName;f.setAttribute("src",b);f.altIcon=d;a!=null&&f.setAttribute("title",a);mxEvent.addListener(f,"click",mxUtils.bind(this,function(){var a=this.selectedMode.altIcon;if(a!=null){this.selectedMode.altIcon=this.selectedMode.getAttribute("src");this.selectedMode.setAttribute("src",a)}else this.selectedMode.className=this.selectedMode.initialClassName;
-if(this.updateDefaultMode)this.defaultMode=f;this.selectedMode=f;a=f.altIcon;if(a!=null){f.altIcon=f.getAttribute("src");f.setAttribute("src",a)}else f.className=f.initialClassName+"Selected";this.fireEvent(new mxEventObject(mxEvent.SELECT));c()}));this.container.appendChild(f);if(this.defaultMode==null){this.defaultMode=f;this.selectMode(f);c()}return f};
-mxToolbar.prototype.addMode=function(a,b,c,d,e,f){var f=f!=null?f:true,g=document.createElement(b!=null?"img":"button");g.initialClassName=e||"mxToolbarMode";g.className=g.initialClassName;g.setAttribute("src",b);g.altIcon=d;a!=null&&g.setAttribute("title",a);if(this.enabled&&f){mxEvent.addListener(g,"click",mxUtils.bind(this,function(){this.selectMode(g,c);this.noReset=false}));mxEvent.addListener(g,"dblclick",mxUtils.bind(this,function(){this.selectMode(g,c);this.noReset=true}));if(this.defaultMode==
-null){this.defaultMode=g;this.defaultFunction=c;this.selectMode(g,c)}}this.container.appendChild(g);return g};
-mxToolbar.prototype.selectMode=function(a,b){if(this.selectedMode!=a){if(this.selectedMode!=null){var c=this.selectedMode.altIcon;if(c!=null){this.selectedMode.altIcon=this.selectedMode.getAttribute("src");this.selectedMode.setAttribute("src",c)}else this.selectedMode.className=this.selectedMode.initialClassName}this.selectedMode=a;c=this.selectedMode.altIcon;if(c!=null){this.selectedMode.altIcon=this.selectedMode.getAttribute("src");this.selectedMode.setAttribute("src",c)}else this.selectedMode.className=
-this.selectedMode.initialClassName+"Selected";this.fireEvent(new mxEventObject(mxEvent.SELECT,"function",b))}};mxToolbar.prototype.resetMode=function(a){(a||!this.noReset)&&this.selectedMode!=this.defaultMode&&this.selectMode(this.defaultMode,this.defaultFunction)};mxToolbar.prototype.addSeparator=function(a){return this.addItem(null,a,null)};mxToolbar.prototype.addBreak=function(){mxUtils.br(this.container)};
-mxToolbar.prototype.addLine=function(){var a=document.createElement("hr");a.style.marginRight="6px";a.setAttribute("size","1");this.container.appendChild(a)};mxToolbar.prototype.destroy=function(){mxEvent.release(this.container);this.selectedMode=this.defaultFunction=this.defaultMode=this.container=null;this.menu!=null&&this.menu.destroy()};
-function mxSession(a,b,c,d){this.model=a;this.urlInit=b;this.urlPoll=c;this.urlNotify=d;if(a!=null){this.codec=new mxCodec;this.codec.lookup=function(b){return a.getCell(b)}}a.addListener(mxEvent.NOTIFY,mxUtils.bind(this,function(a,b){var c=b.getProperty("edit");(c!=null&&this.debug||this.connected&&!this.suspended)&&this.notify("<edit>"+this.encodeChanges(c.changes,c.undone)+"</edit>")}))}mxSession.prototype=new mxEventSource;mxSession.prototype.constructor=mxSession;mxSession.prototype.model=null;
-mxSession.prototype.urlInit=null;mxSession.prototype.urlPoll=null;mxSession.prototype.urlNotify=null;mxSession.prototype.codec=null;mxSession.prototype.linefeed="&#xa;";mxSession.prototype.escapePostData=!0;mxSession.prototype.significantRemoteChanges=!0;mxSession.prototype.sent=0;mxSession.prototype.received=0;mxSession.prototype.debug=!1;mxSession.prototype.connected=!1;mxSession.prototype.suspended=!1;mxSession.prototype.polling=!1;
-mxSession.prototype.start=function(){if(this.debug){this.connected=true;this.fireEvent(new mxEventObject(mxEvent.CONNECT))}else this.connected||this.get(this.urlInit,mxUtils.bind(this,function(){this.connected=true;this.fireEvent(new mxEventObject(mxEvent.CONNECT));this.poll()}))};mxSession.prototype.suspend=function(){if(this.connected&&!this.suspended){this.suspended=true;this.fireEvent(new mxEventObject(mxEvent.SUSPEND))}};
-mxSession.prototype.resume=function(){if(this.connected&&this.suspended){this.suspended=false;this.fireEvent(new mxEventObject(mxEvent.RESUME));this.polling||this.poll()}};mxSession.prototype.stop=function(a){if(this.connected)this.connected=false;this.fireEvent(new mxEventObject(mxEvent.DISCONNECT,"reason",a))};
-mxSession.prototype.poll=function(){if(this.connected&&!this.suspended&&this.urlPoll!=null){this.polling=true;this.get(this.urlPoll,mxUtils.bind(this,function(){this.poll()}))}else this.polling=false};
-mxSession.prototype.notify=function(a,b,c){if(a!=null&&a.length>0){if(this.urlNotify!=null)if(this.debug){mxLog.show();mxLog.debug("mxSession.notify: "+this.urlNotify+" xml="+a)}else{a="<message><delta>"+a+"</delta></message>";this.escapePostData&&(a=encodeURIComponent(a));mxUtils.post(this.urlNotify,"xml="+a,b,c)}this.sent=this.sent+a.length;this.fireEvent(new mxEventObject(mxEvent.NOTIFY,"url",this.urlNotify,"xml",a))}};
-mxSession.prototype.get=function(a,b,c){if(typeof mxUtils!="undefined"){var d=mxUtils.bind(this,function(a){c!=null?c(a):this.stop(a)});mxUtils.get(a,mxUtils.bind(this,function(c){if(typeof mxUtils!="undefined")if(c.isReady()&&c.getStatus()!=404){this.received=this.received+c.getText().length;this.fireEvent(new mxEventObject(mxEvent.GET,"url",a,"request",c));if(this.isValidResponse(c)){if(c.getText().length>0){var f=c.getDocumentElement();f==null?d("Invalid response: "+c.getText()):this.receive(f)}b!=
-null&&b(c)}}else d("Response not ready")}),function(){d("Transmission error")})}};mxSession.prototype.isValidResponse=function(a){return a.getText().indexOf("<?php")<0};mxSession.prototype.encodeChanges=function(a,b){for(var c="",d=b?-1:1,e=b?a.length-1:0;e>=0&&e<a.length;e=e+d)var f=this.codec.encode(a[e]),c=c+mxUtils.getXml(f,this.linefeed);return c};
-mxSession.prototype.receive=function(a){if(a!=null&&a.nodeType==mxConstants.NODETYPE_ELEMENT){var b=a.getAttribute("namespace");if(b!=null)this.model.prefix=b+"-";for(b=a.firstChild;b!=null;){var c=b.nodeName.toLowerCase();c=="state"?this.processState(b):c=="delta"&&this.processDelta(b);b=b.nextSibling}this.fireEvent(new mxEventObject(mxEvent.RECEIVE,"node",a))}};mxSession.prototype.processState=function(a){(new mxCodec(a.ownerDocument)).decode(a.firstChild,this.model)};
-mxSession.prototype.processDelta=function(a){for(a=a.firstChild;a!=null;){a.nodeName=="edit"&&this.processEdit(a);a=a.nextSibling}};mxSession.prototype.processEdit=function(a){a=this.decodeChanges(a);if(a.length>0){var b=this.createUndoableEdit(a);this.model.fireEvent(new mxEventObject(mxEvent.CHANGE,"edit",b,"changes",a));this.model.fireEvent(new mxEventObject(mxEvent.UNDO,"edit",b));this.fireEvent(new mxEventObject(mxEvent.FIRED,"edit",b))}};
-mxSession.prototype.createUndoableEdit=function(a){var b=new mxUndoableEdit(this.model,this.significantRemoteChanges);b.changes=a;b.notify=function(){b.source.fireEvent(new mxEventObject(mxEvent.CHANGE,"edit",b,"changes",b.changes));b.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,"edit",b,"changes",b.changes))};return b};mxSession.prototype.decodeChanges=function(a){this.codec.document=a.ownerDocument;for(var b=[],a=a.firstChild;a!=null;){var c=this.decodeChange(a);c!=null&&b.push(c);a=a.nextSibling}return b};
-mxSession.prototype.decodeChange=function(a){var b=null;if(a.nodeType==mxConstants.NODETYPE_ELEMENT){b=a.nodeName=="mxRootChange"?(new mxCodec(a.ownerDocument)).decode(a):this.codec.decode(a);if(b!=null){b.model=this.model;b.execute();a.nodeName=="mxChildChange"&&b.parent==null&&this.cellRemoved(b.child)}}return b};mxSession.prototype.cellRemoved=function(a){this.codec.putObject(a.getId(),a);for(var b=this.model.getChildCount(a),c=0;c<b;c++)this.cellRemoved(this.model.getChildAt(a,c))};
-function mxUndoableEdit(a,b){this.source=a;this.changes=[];this.significant=b!=null?b:true}mxUndoableEdit.prototype.source=null;mxUndoableEdit.prototype.changes=null;mxUndoableEdit.prototype.significant=null;mxUndoableEdit.prototype.undone=!1;mxUndoableEdit.prototype.redone=!1;mxUndoableEdit.prototype.isEmpty=function(){return this.changes.length==0};mxUndoableEdit.prototype.isSignificant=function(){return this.significant};mxUndoableEdit.prototype.add=function(a){this.changes.push(a)};
-mxUndoableEdit.prototype.notify=function(){};mxUndoableEdit.prototype.die=function(){};mxUndoableEdit.prototype.undo=function(){if(!this.undone){for(var a=this.changes.length-1;a>=0;a--){var b=this.changes[a];b.execute!=null?b.execute():b.undo!=null&&b.undo()}this.undone=true;this.redone=false}this.notify()};
-mxUndoableEdit.prototype.redo=function(){if(!this.redone){for(var a=this.changes.length,b=0;b<a;b++){var c=this.changes[b];c.execute!=null?c.execute():c.redo!=null&&c.redo()}this.undone=false;this.redone=true}this.notify()};function mxUndoManager(a){this.size=a!=null?a:100;this.clear()}mxUndoManager.prototype=new mxEventSource;mxUndoManager.prototype.constructor=mxUndoManager;mxUndoManager.prototype.size=null;mxUndoManager.prototype.history=null;mxUndoManager.prototype.indexOfNextAdd=0;
-mxUndoManager.prototype.isEmpty=function(){return this.history.length==0};mxUndoManager.prototype.clear=function(){this.history=[];this.indexOfNextAdd=0;this.fireEvent(new mxEventObject(mxEvent.CLEAR))};mxUndoManager.prototype.canUndo=function(){return this.indexOfNextAdd>0};mxUndoManager.prototype.undo=function(){for(;this.indexOfNextAdd>0;){var a=this.history[--this.indexOfNextAdd];a.undo();if(a.isSignificant()){this.fireEvent(new mxEventObject(mxEvent.UNDO,"edit",a));break}}};
-mxUndoManager.prototype.canRedo=function(){return this.indexOfNextAdd<this.history.length};mxUndoManager.prototype.redo=function(){for(var a=this.history.length;this.indexOfNextAdd<a;){var b=this.history[this.indexOfNextAdd++];b.redo();if(b.isSignificant()){this.fireEvent(new mxEventObject(mxEvent.REDO,"edit",b));break}}};
-mxUndoManager.prototype.undoableEditHappened=function(a){this.trim();this.size>0&&this.size==this.history.length&&this.history.shift();this.history.push(a);this.indexOfNextAdd=this.history.length;this.fireEvent(new mxEventObject(mxEvent.ADD,"edit",a))};mxUndoManager.prototype.trim=function(){if(this.history.length>this.indexOfNextAdd)for(var a=this.history.splice(this.indexOfNextAdd,this.history.length-this.indexOfNextAdd),b=0;b<a.length;b++)a[b].die()};
-var mxUrlConverter=function(){var a=true,b=null,c=null;return{isEnabled:function(){return a},setEnabled:function(b){a=b},getBaseUrl:function(){return b},setBaseUrl:function(a){b=a},getBaseDomain:function(){return b},setBaseDomain:function(a){b=a},convert:function(d){if(a&&d.indexOf("http://")!=0&&d.indexOf("https://")!=0&&d.indexOf("data:image")!=0){if(b==null){c=location.protocol+"//"+location.host;b=c+location.pathname;var e=b.lastIndexOf("/");e>0&&(b=b.substring(0,e+1))}d=d.charAt(0)=="/"?c+d:
-b+d}return d}}};
-function mxPanningManager(a){this.thread=null;this.active=false;this.dy=this.dx=this.t0y=this.t0x=this.tdy=this.tdx=0;this.scrollbars=false;this.scrollTop=this.scrollLeft=0;this.mouseListener={mouseDown:function(){},mouseMove:function(){},mouseUp:mxUtils.bind(this,function(){this.active&&this.stop()})};a.addMouseListener(this.mouseListener);mxEvent.addListener(document,"mouseup",mxUtils.bind(this,function(){this.active&&this.stop()}));var b=mxUtils.bind(this,function(){this.scrollbars=mxUtils.hasScrollbars(a.container);
-this.scrollLeft=a.container.scrollLeft;this.scrollTop=a.container.scrollTop;return window.setInterval(mxUtils.bind(this,function(){this.tdx=this.tdx-this.dx;this.tdy=this.tdy-this.dy;if(this.scrollbars){var b=-a.container.scrollLeft-Math.ceil(this.dx),d=-a.container.scrollTop-Math.ceil(this.dy);a.panGraph(b,d);a.panDx=this.scrollLeft-a.container.scrollLeft;a.panDy=this.scrollTop-a.container.scrollTop;a.fireEvent(new mxEventObject(mxEvent.PAN))}else a.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=a.view.translate.x;this.t0y=a.view.translate.y;this.active=true};this.panTo=function(c,d,e,f){this.active||this.start();this.scrollLeft=a.container.scrollLeft;this.scrollTop=a.container.scrollTop;var f=f!=null?f:0,g=a.container;this.dx=c+(e!=null?e:0)-g.scrollLeft-g.clientWidth;this.dx=this.dx<0&&Math.abs(this.dx)<this.border?
-this.border+this.dx:this.handleMouseOut?Math.max(this.dx,0):0;if(this.dx==0){this.dx=c-g.scrollLeft;this.dx=this.dx>0&&this.dx<this.border?this.dx-this.border:this.handleMouseOut?Math.min(0,this.dx):0}this.dy=d+f-g.scrollTop-g.clientHeight;this.dy=this.dy<0&&Math.abs(this.dy)<this.border?this.border+this.dy:this.handleMouseOut?Math.max(this.dy,0):0;if(this.dy==0){this.dy=d-g.scrollTop;this.dy=this.dy>0&&this.dy<this.border?this.dy-this.border:this.handleMouseOut?Math.min(0,this.dy):0}if(this.dx!=
-0||this.dy!=0){this.dx=this.dx*this.damper;this.dy=this.dy*this.damper;if(this.thread==null)this.thread=b()}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.tdy=this.tdx=0;if(this.scrollbars){a.panDx=0;a.panDy=0;a.fireEvent(new mxEventObject(mxEvent.PAN))}else{var b=a.panDx,d=a.panDy;if(b!=0||d!=0){a.panGraph(0,0);a.view.setTranslate(this.t0x+
-b/a.view.scale,this.t0y+d/a.view.scale)}}}};this.destroy=function(){a.removeMouseListener(this.mouseListener)}}mxPanningManager.prototype.damper=1/6;mxPanningManager.prototype.delay=10;mxPanningManager.prototype.handleMouseOut=!0;mxPanningManager.prototype.border=0;function mxPath(a){this.format=a;this.path=[];this.translate=new mxPoint(0,0)}mxPath.prototype.format=null;mxPath.prototype.translate=null;mxPath.prototype.scale=1;mxPath.prototype.path=null;
-mxPath.prototype.isVml=function(){return this.format=="vml"};mxPath.prototype.getPath=function(){return this.path.join("")};mxPath.prototype.setTranslate=function(a,b){this.translate=new mxPoint(a,b)};mxPath.prototype.moveTo=function(a,b){a=a+this.translate.x;b=b+this.translate.y;a=a*this.scale;b=b*this.scale;this.isVml()?this.path.push("m ",Math.round(a)," ",Math.round(b)," "):this.path.push("M ",a," ",b," ")};
-mxPath.prototype.lineTo=function(a,b){a=a+this.translate.x;b=b+this.translate.y;a=a*this.scale;b=b*this.scale;this.isVml()?this.path.push("l ",Math.round(a)," ",Math.round(b)," "):this.path.push("L ",a," ",b," ")};
-mxPath.prototype.quadTo=function(a,b,c,d){a=a+this.translate.x;b=b+this.translate.y;a=a*this.scale;b=b*this.scale;c=c+this.translate.x;d=d+this.translate.y;c=c*this.scale;d=d*this.scale;this.isVml()?this.path.push("c ",Math.round(a)," ",Math.round(b)," ",Math.round(c)," ",Math.round(d)," ",Math.round(c)," ",Math.round(d)," "):this.path.push("Q ",a," ",b," ",c," ",d," ")};
-mxPath.prototype.curveTo=function(a,b,c,d,e,f){a=a+this.translate.x;b=b+this.translate.y;a=a*this.scale;b=b*this.scale;c=c+this.translate.x;d=d+this.translate.y;c=c*this.scale;d=d*this.scale;e=e+this.translate.x;f=f+this.translate.y;e=e*this.scale;f=f*this.scale;this.isVml()?this.path.push("c ",Math.round(a)," ",Math.round(b)," ",Math.round(c)," ",Math.round(d)," ",Math.round(e)," ",Math.round(f)," "):this.path.push("C ",a," ",b," ",c," ",d," ",e," ",f," ")};
-mxPath.prototype.ellipse=function(a,b,c,d){a=a+this.translate.x;b=b+this.translate.y;a=a*this.scale;b=b*this.scale;if(this.isVml())this.path.push("at ",Math.round(a)," ",Math.round(b)," ",Math.round(a+c)," ",Math.round(b+d)," ",Math.round(a)," ",Math.round(b+d/2)," ",Math.round(a)," ",Math.round(b+d/2));else{var e=a,f=b+d/2,a=a+c,b=b+d/2,c=c/2,d=d/2;this.path.push("M ",e," ",f," ");this.path.push("A ",c," ",d," 0 1 0 ",a," ",b," ");this.path.push("A ",c," ",d," 0 1 0 ",e," ",f)}};
-mxPath.prototype.addPath=function(a){this.path=this.path.concat(a.path)};mxPath.prototype.write=function(a){this.path.push(a," ")};mxPath.prototype.end=function(){this.format=="vml"&&this.path.push("e")};mxPath.prototype.close=function(){this.format=="vml"?this.path.push("x e"):this.path.push("Z")};function mxPopupMenu(a){this.factoryMethod=a;a!=null&&this.init()}mxPopupMenu.prototype=new mxEventSource;mxPopupMenu.prototype.constructor=mxPopupMenu;
-mxPopupMenu.prototype.submenuImage=mxClient.imageBasePath+"/submenu.gif";mxPopupMenu.prototype.zIndex=10006;mxPopupMenu.prototype.factoryMethod=null;mxPopupMenu.prototype.useLeftButtonForPopup=!1;mxPopupMenu.prototype.enabled=!0;mxPopupMenu.prototype.itemCount=0;mxPopupMenu.prototype.autoExpand=!1;mxPopupMenu.prototype.smartSeparators=!1;mxPopupMenu.prototype.labels=!0;
-mxPopupMenu.prototype.init=function(){this.table=document.createElement("table");this.table.className="mxPopupMenu";this.tbody=document.createElement("tbody");this.table.appendChild(this.tbody);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);mxEvent.disableContextMenu(this.div)};mxPopupMenu.prototype.isEnabled=function(){return this.enabled};
-mxPopupMenu.prototype.setEnabled=function(a){this.enabled=a};mxPopupMenu.prototype.isPopupTrigger=function(a){return a.isPopupTrigger()||this.useLeftButtonForPopup&&mxEvent.isLeftMouseButton(a.getEvent())};
-mxPopupMenu.prototype.addItem=function(a,b,c,d,e,f){d=d||this;this.itemCount++;if(d.willAddSeparator){d.containsItems&&this.addSeparator(d,true);d.willAddSeparator=false}d.containsItems=true;var g=document.createElement("tr");g.className="mxPopupMenuItem";var h=document.createElement("td");h.className="mxPopupMenuIcon";if(b!=null){e=document.createElement("img");e.src=b;h.appendChild(e)}else if(e!=null){b=document.createElement("div");b.className=e;h.appendChild(b)}g.appendChild(h);if(this.labels){h=
-document.createElement("td");h.className="mxPopupMenuItem"+(f!=null&&!f?" disabled":"");mxUtils.write(h,a);h.align="left";g.appendChild(h);a=document.createElement("td");a.className="mxPopupMenuItem"+(f!=null&&!f?" disabled":"");a.style.paddingRight="6px";a.style.textAlign="right";g.appendChild(a);d.div==null&&this.createSubmenu(d)}d.tbody.appendChild(g);if(f==null||f){f=mxClient.IS_TOUCH?"touchmove":"mousemove";a=mxClient.IS_TOUCH?"touchend":"mouseup";mxEvent.addListener(g,mxClient.IS_TOUCH?"touchstart":
-"mousedown",mxUtils.bind(this,function(a){this.eventReceiver=g;if(d.activeRow!=g&&d.activeRow!=d){d.activeRow!=null&&d.activeRow.div.parentNode!=null&&this.hideSubmenu(d);if(g.div!=null){this.showSubmenu(d,g);d.activeRow=g}}mxEvent.consume(a)}));mxEvent.addListener(g,f,mxUtils.bind(this,function(){if(d.activeRow!=g&&d.activeRow!=d){d.activeRow!=null&&d.activeRow.div.parentNode!=null&&this.hideSubmenu(d);if(this.autoExpand&&g.div!=null){this.showSubmenu(d,g);d.activeRow=g}}g.className="mxPopupMenuItemHover"}));
-mxEvent.addListener(g,a,mxUtils.bind(this,function(a){if(this.eventReceiver==g){d.activeRow!=g&&this.hideMenu();c!=null&&c(a)}this.eventReceiver=null;mxEvent.consume(a)}));mxEvent.addListener(g,"mouseout",mxUtils.bind(this,function(){g.className="mxPopupMenuItem"}))}return g};
-mxPopupMenu.prototype.createSubmenu=function(a){a.table=document.createElement("table");a.table.className="mxPopupMenu";a.tbody=document.createElement("tbody");a.table.appendChild(a.tbody);a.div=document.createElement("div");a.div.className="mxPopupMenu";a.div.style.position="absolute";a.div.style.display="inline";a.div.style.zIndex=this.zIndex;a.div.appendChild(a.table);var b=document.createElement("img");b.setAttribute("src",this.submenuImage);td=a.firstChild.nextSibling.nextSibling;td.appendChild(b)};
-mxPopupMenu.prototype.showSubmenu=function(a,b){if(b.div!=null){b.div.style.left=a.div.offsetLeft+b.offsetLeft+b.offsetWidth-1+"px";b.div.style.top=a.div.offsetTop+b.offsetTop+"px";document.body.appendChild(b.div);var c=parseInt(b.div.offsetLeft),d=parseInt(b.div.offsetWidth),e=document.body,f=document.documentElement;if(c+d>(e.scrollLeft||f.scrollLeft)+(e.clientWidth||f.clientWidth))b.div.style.left=a.div.offsetLeft-d+(mxClient.IS_IE?6:-6)+"px";mxUtils.fit(b.div)}};
-mxPopupMenu.prototype.addSeparator=function(a,b){a=a||this;if(this.smartSeparators&&!b)a.willAddSeparator=true;else if(a.tbody!=null){a.willAddSeparator=false;var c=document.createElement("tr"),d=document.createElement("td");d.className="mxPopupMenuIcon";d.style.padding="0 0 0 0px";c.appendChild(d);d=document.createElement("td");d.style.padding="0 0 0 0px";d.setAttribute("colSpan","2");var e=document.createElement("hr");e.setAttribute("size","1");d.appendChild(e);c.appendChild(d);a.tbody.appendChild(c)}};
-mxPopupMenu.prototype.popup=function(a,b,c,d){if(this.div!=null&&this.tbody!=null&&this.factoryMethod!=null){this.div.style.left=a+"px";for(this.div.style.top=b+"px";this.tbody.firstChild!=null;){mxEvent.release(this.tbody.firstChild);this.tbody.removeChild(this.tbody.firstChild)}this.itemCount=0;this.factoryMethod(this,c,d);if(this.itemCount>0){this.showMenu();this.fireEvent(new mxEventObject(mxEvent.SHOW))}}};
-mxPopupMenu.prototype.isMenuShowing=function(){return this.div!=null&&this.div.parentNode==document.body};mxPopupMenu.prototype.showMenu=function(){if(document.documentMode>=9)this.div.style.filter="none";document.body.appendChild(this.div);mxUtils.fit(this.div)};mxPopupMenu.prototype.hideMenu=function(){if(this.div!=null){this.div.parentNode!=null&&this.div.parentNode.removeChild(this.div);this.hideSubmenu(this);this.containsItems=false}};
-mxPopupMenu.prototype.hideSubmenu=function(a){if(a.activeRow!=null){this.hideSubmenu(a.activeRow);a.activeRow.div.parentNode!=null&&a.activeRow.div.parentNode.removeChild(a.activeRow.div);a.activeRow=null}};mxPopupMenu.prototype.destroy=function(){if(this.div!=null){mxEvent.release(this.div);this.div.parentNode!=null&&this.div.parentNode.removeChild(this.div);this.div=null}};
-function mxAutoSaveManager(a){this.changeHandler=mxUtils.bind(this,function(a,c){this.isEnabled()&&this.graphModelChanged(c.getProperty("edit").changes)});this.setGraph(a)}mxAutoSaveManager.prototype=new mxEventSource;mxAutoSaveManager.prototype.constructor=mxAutoSaveManager;mxAutoSaveManager.prototype.graph=null;mxAutoSaveManager.prototype.autoSaveDelay=10;mxAutoSaveManager.prototype.autoSaveThrottle=2;mxAutoSaveManager.prototype.autoSaveThreshold=5;mxAutoSaveManager.prototype.ignoredChanges=0;
-mxAutoSaveManager.prototype.lastSnapshot=0;mxAutoSaveManager.prototype.enabled=!0;mxAutoSaveManager.prototype.changeHandler=null;mxAutoSaveManager.prototype.isEnabled=function(){return this.enabled};mxAutoSaveManager.prototype.setEnabled=function(a){this.enabled=a};mxAutoSaveManager.prototype.setGraph=function(a){this.graph!=null&&this.graph.getModel().removeListener(this.changeHandler);this.graph=a;this.graph!=null&&this.graph.getModel().addListener(mxEvent.CHANGE,this.changeHandler)};
-mxAutoSaveManager.prototype.save=function(){};mxAutoSaveManager.prototype.graphModelChanged=function(){var a=((new Date).getTime()-this.lastSnapshot)/1E3;if(a>this.autoSaveDelay||this.ignoredChanges>=this.autoSaveThreshold&&a>this.autoSaveThrottle){this.save();this.reset()}else this.ignoredChanges++};mxAutoSaveManager.prototype.reset=function(){this.lastSnapshot=(new Date).getTime();this.ignoredChanges=0};mxAutoSaveManager.prototype.destroy=function(){this.setGraph(null)};
-function mxAnimation(a){this.delay=a!=null?a:20}mxAnimation.prototype=new mxEventSource;mxAnimation.prototype.constructor=mxAnimation;mxAnimation.prototype.delay=null;mxAnimation.prototype.thread=null;mxAnimation.prototype.startAnimation=function(){if(this.thread==null)this.thread=window.setInterval(mxUtils.bind(this,this.updateAnimation),this.delay)};mxAnimation.prototype.updateAnimation=function(){this.fireEvent(new mxEventObject(mxEvent.EXECUTE))};
-mxAnimation.prototype.stopAnimation=function(){if(this.thread!=null){window.clearInterval(this.thread);this.thread=null;this.fireEvent(new mxEventObject(mxEvent.DONE))}};function mxMorphing(a,b,c,d){mxAnimation.call(this,d);this.graph=a;this.steps=b!=null?b:6;this.ease=c!=null?c:1.5}mxMorphing.prototype=new mxAnimation;mxMorphing.prototype.constructor=mxMorphing;mxMorphing.prototype.graph=null;mxMorphing.prototype.steps=null;mxMorphing.prototype.step=0;mxMorphing.prototype.ease=null;
-mxMorphing.prototype.cells=null;mxMorphing.prototype.updateAnimation=function(){var a=new mxCellStatePreview(this.graph);if(this.cells!=null)for(var b=0;b<this.cells.length;b++)this.animateCell(cells[b],a,false);else this.animateCell(this.graph.getModel().getRoot(),a,true);this.show(a);(a.isEmpty()||this.step++>=this.steps)&&this.stopAnimation()};mxMorphing.prototype.show=function(a){a.show()};
-mxMorphing.prototype.animateCell=function(a,b,c){var d=this.graph.getView().getState(a),e=null;if(d!=null){e=this.getDelta(d);if(this.graph.getModel().isVertex(a)&&(e.x!=0||e.y!=0)){var f=this.graph.view.getTranslate(),g=this.graph.view.getScale();e.x=e.x+f.x*g;e.y=e.y+f.y*g;b.moveState(d,-e.x/this.ease,-e.y/this.ease)}}if(c&&!this.stopRecursion(d,e)){d=this.graph.getModel().getChildCount(a);for(e=0;e<d;e++)this.animateCell(this.graph.getModel().getChildAt(a,e),b,c)}};
-mxMorphing.prototype.stopRecursion=function(a,b){return b!=null&&(b.x!=0||b.y!=0)};mxMorphing.prototype.getDelta=function(a){var b=this.getOriginForCell(a.cell),c=this.graph.getView().getTranslate(),d=this.graph.getView().getScale(),a=new mxPoint(a.x/d-c.x,a.y/d-c.y);return new mxPoint((b.x-a.x)*d,(b.y-a.y)*d)};
-mxMorphing.prototype.getOriginForCell=function(a){var b=null;if(a!=null){b=this.getOriginForCell(this.graph.getModel().getParent(a));a=this.graph.getCellGeometry(a);if(a!=null){b.x=b.x+a.x;b.y=b.y+a.y}}if(b==null){b=this.graph.view.getTranslate();b=new mxPoint(-b.x,-b.y)}return b};function mxImageBundle(a){this.images=[];this.alt=a!=null?a:false}mxImageBundle.prototype.images=null;mxImageBundle.prototype.images=null;mxImageBundle.prototype.putImage=function(a,b,c){this.images[a]={value:b,fallback:c}};
-mxImageBundle.prototype.getImage=function(a){var b=null;if(a!=null){a=this.images[a];a!=null&&(b=this.alt?a.fallback:a.value)}return b};function mxImageExport(){this.initShapes();this.initMarkers()}mxImageExport.prototype.includeOverlays=!1;mxImageExport.prototype.glassSize=0.4;mxImageExport.prototype.shapes=null;mxImageExport.prototype.markers=null;
-mxImageExport.prototype.drawState=function(a,b){if(a!=null){if(a.shape!=null){var c=a.shape.stencil!=null?a.shape.stencil:this.shapes[a.style[mxConstants.STYLE_SHAPE]];c==null&&(typeof a.shape.redrawPath=="function"?c=this.createShape(a,b):a.view.graph.getModel().isVertex(a.cell)&&(c=this.shapes.rectangle));if(c!=null){this.drawShape(a,b,c);this.includeOverlays&&this.drawOverlays(a,b)}}for(var c=a.view.graph,d=c.model.getChildCount(a.cell),e=0;e<d;e++)this.drawState(c.view.getState(c.model.getChildAt(a.cell,
-e)),b)}};
-mxImageExport.prototype.createShape=function(){return{drawShape:function(a,b,c,d){var e={translate:new mxPoint(c.x,c.y),moveTo:function(b,c){a.moveTo(this.translate.x+b,this.translate.y+c)},lineTo:function(b,c){a.lineTo(this.translate.x+b,this.translate.y+c)},quadTo:function(b,c,d,e){a.quadTo(this.translate.x+b,this.translate.y+c,this.translate.x+d,this.translate.y+e)},curveTo:function(b,c,d,e,i,l){a.curveTo(this.translate.x+b,this.translate.y+c,this.translate.x+d,this.translate.y+e,this.translate.x+i,
-this.translate.y+l)},end:function(){},close:function(){a.close()}};d||a.fillAndStroke();a.begin();b.shape.redrawPath.call(b.shape,e,c.x,c.y,c.width,c.height,!d);d||a.fillAndStroke();return true}}};mxImageExport.prototype.drawOverlays=function(a,b){a.overlays!=null&&a.overlays.visit(function(a,d){var e=d.bounds;e!=null&&b.image(e.x,e.y,e.width,e.height,d.image)})};
-mxImageExport.prototype.drawShape=function(a,b,c){var d=mxUtils.getNumber(a.style,mxConstants.STYLE_ROTATION,0),e=mxUtils.getValue(a.style,mxConstants.STYLE_DIRECTION,null),f=a.style[mxConstants.STYLE_STENCIL_FLIPH],g=a.style[mxConstants.STYLE_STENCIL_FLIPV];if(f?!g:g)d=d*-1;e!=null&&(e=="north"?d=d+270:e=="west"?d=d+180:e=="south"&&(d=d+90));if(f&&g){d=d+180;g=f=false}b.save();d=d%360;(d!=0||f||g)&&b.rotate(d,f,g,a.getCenterX(),a.getCenterY());var h=a.view.scale,k=mxUtils.getNumber(a.style,mxConstants.STYLE_STROKEWIDTH,
-1)*h;b.setStrokeWidth(k);var i=k/2,h=this.getBackgroundBounds(a);if(a.shape.stencil==null&&(e=="south"||e=="north")){var l=(h.width-h.height)/2;h.x=h.x+l;h.y=h.y+-l;l=h.width;h.width=h.height;h.height=l}var l=new mxRectangle(h.x-i,h.y-i,h.width+k,h.height+k),m=mxUtils.getValue(a.style,mxConstants.STYLE_OPACITY,100)/100,k=a.style[mxConstants.STYLE_SHAPE],n=(i=k==mxConstants.SHAPE_IMAGE)?null:mxUtils.getValue(a.style,mxConstants.STYLE_GRADIENTCOLOR);n==mxConstants.NONE&&(n=null);var o=mxUtils.getValue(a.style,
-i?mxConstants.STYLE_IMAGE_BACKGROUND:mxConstants.STYLE_FILLCOLOR,null);o==mxConstants.NONE&&(o=null);var p=mxUtils.getValue(a.style,i?mxConstants.STYLE_IMAGE_BORDER:mxConstants.STYLE_STROKECOLOR,null);p==mxConstants.NONE&&(p=null);var q=o!=null&&(k==mxConstants.SHAPE_LABEL||k==mxConstants.SHAPE_RECTANGLE);mxUtils.getValue(a.style,mxConstants.STYLE_SHADOW,false)&&this.drawShadow(b,a,c,d,f,g,h,m,o!=null);b.setAlpha(m);if(mxUtils.getValue(a.style,mxConstants.STYLE_DASHED,"0")=="1"){b.setDashed(true);
-d=a.style.dashPattern;d!=null&&b.setDashPattern(d)}if(p!=null||o!=null){p!=null&&b.setStrokeColor(p);o!=null&&(n!=null&&n!="transparent"?b.setGradient(o,n,h.x,h.y,h.width,h.height,e):b.setFillColor(o));q=c.drawShape(b,a,h,true,false)&&q;c.drawShape(b,a,h,false,false)}q&&mxUtils.getValue(a.style,mxConstants.STYLE_GLASS,0)==1&&this.drawGlass(a,b,l,c,this.glassSize);if(i||k==mxConstants.SHAPE_LABEL){c=a.view.graph.getImage(a);if(c!=null){e=this.getImageBounds(a);e!=null&&this.drawImage(a,b,e,c)}}b.restore();
-f=a.text;c=a.view.graph.getLabel(a.cell);if(f!=null&&c!=null&&c.length>0){b.save();b.setAlpha(mxUtils.getValue(a.style,mxConstants.STYLE_TEXT_OPACITY,100)/100);e=new mxRectangle(f.boundingBox.x,f.boundingBox.y,f.boundingBox.width,f.boundingBox.height);d=mxUtils.getValue(a.style,mxConstants.STYLE_HORIZONTAL,1)==0;e.y=e.y+2;if(d)if(f.dialect!=mxConstants.DIALECT_SVG){g=e.x+e.width/2;f=e.y+e.height/2;l=e.width;e.width=e.height;e.height=l;e.x=g-e.width/2;e.y=f-e.height/2}else if(f.dialect==mxConstants.DIALECT_SVG){h=
-a.y+a.height;g=e.getCenterX()-a.x;f=e.getCenterY()-a.y;g=h-g-e.height/2;e.x=a.x+f-e.width/2;e.y=g}this.drawLabelBackground(a,b,e,d);this.drawLabel(a,b,e,d,c);b.restore()}};
-mxImageExport.prototype.drawShadow=function(a,b,c,d,e,f,g,h,k){var i=d*Math.PI/180,d=Math.cos(-i),i=Math.sin(-i),d=mxUtils.getRotatedPoint(new mxPoint(mxConstants.SHADOW_OFFSET_X,mxConstants.SHADOW_OFFSET_Y),d,i);if(e)d.x=d.x*-1;if(f)d.y=d.y*-1;a.translate(d.x,d.y);if(c.drawShape(a,b,g,true,true)){a.setAlpha(mxConstants.SHADOW_OPACITY*h);a.shadow(mxConstants.SHADOWCOLOR,k)}a.translate(-d.x,-d.y)};
-mxImageExport.prototype.drawGlass=function(a,b,c,d,e){if(d.drawShape(b,a,c,true,false)){b.save();b.clip();b.setGlassGradient(c.x,c.y,c.width,c.height);b.begin();b.moveTo(c.x,c.y);b.lineTo(c.x,c.y+c.height*e);b.quadTo(c.x+c.width*0.5,c.y+c.height*0.7,c.x+c.width,c.y+c.height*e);b.lineTo(c.x+c.width,c.y);b.close();b.fill();b.restore()}};
-mxImageExport.prototype.drawImage=function(a,b,c,d){var e=mxUtils.getValue(a.style,mxConstants.STYLE_IMAGE_ASPECT,1)==1,f=mxUtils.getValue(a.style,mxConstants.STYLE_IMAGE_FLIPH,0)==1,a=mxUtils.getValue(a.style,mxConstants.STYLE_IMAGE_FLIPV,0)==1;b.image(c.x,c.y,c.width,c.height,d,e,f,a)};
-mxImageExport.prototype.drawLabelBackground=function(a,b,c,d){var e=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_BORDERCOLOR),f=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_BACKGROUNDCOLOR);e==mxConstants.NONE&&(e=null);f==mxConstants.NONE&&(f=null);if(e!=null||f!=null){var g=c.x,a=c.y-mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_PADDING,0),h=c.width,c=c.height;if(d){g=g+(h-c)/2;a=a+(c-h)/2;d=h;h=c;c=d}f!=null&&b.setFillColor(f);if(e!=null){b.setStrokeColor(e);b.setStrokeWidth(1);b.setDashed(false)}b.rect(g,
-a,h,c);f!=null&&e!=null?b.fillAndStroke():f!=null?b.fill():e!=null&&b.stroke()}};
-mxImageExport.prototype.drawLabel=function(a,b,c,d,e){var f=a.view.scale;b.setFontColor(mxUtils.getValue(a.style,mxConstants.STYLE_FONTCOLOR,"#000000"));b.setFontFamily(mxUtils.getValue(a.style,mxConstants.STYLE_FONTFAMILY,mxConstants.DEFAULT_FONTFAMILY));b.setFontStyle(mxUtils.getValue(a.style,mxConstants.STYLE_FONTSTYLE,0));b.setFontSize(mxUtils.getValue(a.style,mxConstants.STYLE_FONTSIZE,mxConstants.DEFAULT_FONTSIZE)*f);f=mxUtils.getValue(a.style,mxConstants.STYLE_ALIGN,mxConstants.ALIGN_LEFT);
-f=="left"&&(f=null);var g=c.y-mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_PADDING,0),h=a.view.graph.isWrapping(a.cell);(a=a.view.graph.isHtmlLabel(a.cell))&&mxText.prototype.replaceLinefeeds&&(e=e.replace(/\n/g,"<br/>"));b.text(c.x,g,c.width,c.height,e,f,null,d,h,a?"html":"")};
-mxImageExport.prototype.getBackgroundBounds=function(a){if(a.style[mxConstants.STYLE_SHAPE]==mxConstants.SHAPE_SWIMLANE){var b=a.view.scale,b=mxUtils.getValue(a.style,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE)*b,c=a.width,d=a.height;mxUtils.getValue(a.style,mxConstants.STYLE_HORIZONTAL,true)?d=b:c=b;return new mxRectangle(a.x,a.y,Math.min(a.width,c),Math.min(a.height,d))}return new mxRectangle(a.x,a.y,a.width,a.height)};
-mxImageExport.prototype.getImageBounds=function(a){var b=new mxRectangle(a.x,a.y,a.width,a.height),c=a.style;if(mxUtils.getValue(c,mxConstants.STYLE_SHAPE)!=mxConstants.SHAPE_IMAGE){var a=mxUtils.getValue(c,mxConstants.STYLE_IMAGE_ALIGN,mxConstants.ALIGN_LEFT),d=mxUtils.getValue(c,mxConstants.STYLE_IMAGE_VERTICAL_ALIGN,mxConstants.ALIGN_MIDDLE),e=mxUtils.getValue(c,mxConstants.STYLE_IMAGE_WIDTH,mxConstants.DEFAULT_IMAGESIZE),f=mxUtils.getValue(c,mxConstants.STYLE_IMAGE_HEIGHT,mxConstants.DEFAULT_IMAGESIZE),
-c=mxUtils.getValue(c,mxConstants.STYLE_SPACING,2);b.x=a==mxConstants.ALIGN_CENTER?b.x+(b.width-e)/2:a==mxConstants.ALIGN_RIGHT?b.x+(b.width-e-c-2):b.x+(c+4);b.y=d==mxConstants.ALIGN_TOP?b.y+c:d==mxConstants.ALIGN_BOTTOM?b.y+(b.height-f-c):b.y+(b.height-f)/2;b.width=e;b.height=f}return b};
-mxImageExport.prototype.drawMarker=function(a,b,c){var d=null,e=b.absolutePoints,f=e.length,g=c?e[1]:e[f-2],e=c?e[0]:e[f-1],f=e.x-g.x,h=e.y-g.y,k=Math.max(1,Math.sqrt(f*f+h*h)),g=f/k,f=h/k,h=mxUtils.getValue(b.style,c?mxConstants.STYLE_STARTSIZE:mxConstants.STYLE_ENDSIZE,mxConstants.DEFAULT_MARKERSIZE),k=mxUtils.getValue(b.style,mxConstants.STYLE_STROKEWIDTH,1),e=e.clone(),i=mxUtils.getValue(b.style,c?mxConstants.STYLE_STARTARROW:mxConstants.STYLE_ENDARROW),l=this.markers[i];l!=null&&(d=l(a,b,i,e,
-g,f,h,c,k));return d};
-mxImageExport.prototype.initShapes=function(){this.shapes=[];this.shapes.rectangle={drawShape:function(a,c,d,e){if(e){if(mxUtils.getValue(c.style,mxConstants.STYLE_ROUNDED,false)){c=mxUtils.getValue(c.style,mxConstants.STYLE_ARCSIZE,mxConstants.RECTANGLE_ROUNDING_FACTOR*100)/100;c=Math.min(d.width*c,d.height*c);a.roundrect(d.x,d.y,d.width,d.height,c,c)}else a.rect(d.x,d.y,d.width,d.height);return true}a.fillAndStroke()}};this.shapes.swimlane={drawShape:function(a,c,d,e){if(e){if(mxUtils.getValue(c.style,mxConstants.STYLE_ROUNDED,
-false)){c=Math.min(d.width*mxConstants.RECTANGLE_ROUNDING_FACTOR,d.height*mxConstants.RECTANGLE_ROUNDING_FACTOR);a.roundrect(d.x,d.y,d.width,d.height,c,c)}else a.rect(d.x,d.y,d.width,d.height);return true}a.fillAndStroke();a.begin();var e=c.x,f=c.y,g=c.width,h=c.height;if(mxUtils.getValue(c.style,mxConstants.STYLE_HORIZONTAL,1)==0){e=e+d.width;g=g-d.width;a.moveTo(e,f);a.lineTo(e+g,f);a.lineTo(e+g,f+h);a.lineTo(e,f+h)}else{f=f+d.height;h=h-d.height;a.moveTo(e,f);a.lineTo(e,f+h);a.lineTo(e+g,f+h);
-a.lineTo(e+g,f)}a.stroke()}};this.shapes.image=this.shapes.rectangle;this.shapes.label=this.shapes.rectangle;var a=this;this.shapes.connector={translatePoint:function(a,c,d){if(d!=null){var e=a[c].clone();e.x=e.x+d.x;e.y=e.y+d.y;a[c]=e}},drawShape:function(b,c,d,e,f){if(e){d=mxUtils.getValue(c.style,mxConstants.STYLE_ROUNDED,false);e=mxConstants.LINE_ARCSIZE/2;b.setFillColor(f?mxConstants.NONE:mxUtils.getValue(c.style,mxConstants.STYLE_STROKECOLOR,"#000000"));b.setDashed(false);f=c.absolutePoints.slice();
-this.translatePoint(f,0,a.drawMarker(b,c,true));this.translatePoint(f,f.length-1,a.drawMarker(b,c,false));b.setDashed(mxUtils.getValue(c.style,mxConstants.STYLE_DASHED,"0")=="1");var g=f[0],c=f[f.length-1];b.begin();b.moveTo(g.x,g.y);for(var h=1;h<f.length-1;h++){var k=f[h],i=g.x-k.x,g=g.y-k.y;if(d&&h<f.length-1&&(i!=0||g!=0)){var l=Math.sqrt(i*i+g*g),i=i*Math.min(e,l/2)/l,g=g*Math.min(e,l/2)/l;b.lineTo(k.x+i,k.y+g);g=f[h+1];i=g.x-k.x;g=g.y-k.y;l=Math.max(1,Math.sqrt(i*i+g*g));i=i*Math.min(e,l/2)/
-l;g=g*Math.min(e,l/2)/l;i=k.x+i;g=k.y+g;b.curveTo(k.x,k.y,k.x,k.y,i,g);k=new mxPoint(i,g)}else b.lineTo(k.x,k.y);g=k}b.lineTo(c.x,c.y);b.stroke();return true}}};this.shapes.arrow={drawShape:function(a,c,d,e){if(e){var d=mxConstants.ARROW_SPACING,f=mxConstants.ARROW_WIDTH,e=mxConstants.ARROW_SIZE,c=c.absolutePoints,g=c[0],c=c[c.length-1],h=c.x-g.x,k=c.y-g.y,i=Math.sqrt(h*h+k*k),l=i-2*d-e,e=h/i,k=k/i,i=f*k/3,f=-f*e/3,h=g.x-i/2+d*e,g=g.y-f/2+d*k,m=h+i,n=g+f,o=m+l*e,l=n+l*k,p=o+i,q=l+f,t=p-3*i,u=q-3*
-f;a.begin();a.moveTo(h,g);a.lineTo(m,n);a.lineTo(o,l);a.lineTo(p,q);a.lineTo(c.x-d*e,c.y-d*k);a.lineTo(t,u);a.lineTo(t+i,u+f);a.close();return true}a.fillAndStroke()}};this.shapes.cylinder={drawShape:function(a,c,d,e){if(e)return false;var c=d.x,e=d.y,f=d.width,d=d.height,g=Math.min(mxCylinder.prototype.maxHeight,Math.floor(d/5));a.begin();a.moveTo(c,e+g);a.curveTo(c,e-g/3,c+f,e-g/3,c+f,e+g);a.lineTo(c+f,e+d-g);a.curveTo(c+f,e+d+g/3,c,e+d+g/3,c,e+d-g);a.close();a.fillAndStroke();a.begin();a.moveTo(c,
-e+g);a.curveTo(c,e+2*g,c+f,e+2*g,c+f,e+g);a.stroke()}};this.shapes.line={drawShape:function(a,c,d,e){if(e)return false;a.begin();c=c.getCenterY();a.moveTo(d.x,c);a.lineTo(d.x+d.width,c);a.stroke()}};this.shapes.ellipse={drawShape:function(a,c,d,e){if(e){a.ellipse(d.x,d.y,d.width,d.height);return true}a.fillAndStroke()}};this.shapes.doubleEllipse={drawShape:function(a,c,d,e){var c=d.x,f=d.y,g=d.width,d=d.height;if(e){a.ellipse(c,f,g,d);return true}a.fillAndStroke();e=Math.min(4,Math.min(g/5,d/5));
-g=g-2*e;d=d-2*e;g>0&&d>0&&a.ellipse(c+e,f+e,g,d);a.stroke()}};this.shapes.triangle={drawShape:function(a,c,d,e){if(e){var c=d.x,e=d.y,f=d.width,d=d.height;a.begin();a.moveTo(c,e);a.lineTo(c+f,e+d/2);a.lineTo(c,e+d);a.close();return true}a.fillAndStroke()}};this.shapes.rhombus={drawShape:function(a,c,d,e){if(e){var c=d.x,e=d.y,f=d.width,d=d.height,g=f/2,h=d/2;a.begin();a.moveTo(c+g,e);a.lineTo(c+f,e+h);a.lineTo(c+g,e+d);a.lineTo(c,e+h);a.close();return true}a.fillAndStroke()}};this.shapes.hexagon=
-{drawShape:function(a,c,d,e){if(e){var c=d.x,e=d.y,f=d.width,d=d.height;a.begin();a.moveTo(c+0.25*f,e);a.lineTo(c+0.75*f,e);a.lineTo(c+f,e+0.5*d);a.lineTo(c+0.75*f,e+d);a.lineTo(c+0.25*f,e+d);a.lineTo(c,e+0.5*d);a.close();return true}a.fillAndStroke()}};this.shapes.actor={drawShape:function(a,c,d,e){if(e){var c=d.x,e=d.y,f=d.width,d=d.height,g=f*2/6;a.begin();a.moveTo(c,e+d);a.curveTo(c,e+3*d/5,c,e+2*d/5,c+f/2,e+2*d/5);a.curveTo(c+f/2-g,e+2*d/5,c+f/2-g,e,c+f/2,e);a.curveTo(c+f/2+g,e,c+f/2+g,e+2*d/
-5,c+f/2,e+2*d/5);a.curveTo(c+f,e+2*d/5,c+f,e+3*d/5,c+f,e+d);a.close();return true}a.fillAndStroke()}};this.shapes.cloud={drawShape:function(a,c,d,e){if(e){var c=d.x,e=d.y,f=d.width,d=d.height;a.begin();a.moveTo(c+0.25*f,e+0.25*d);a.curveTo(c+0.05*f,e+0.25*d,c,e+0.5*d,c+0.16*f,e+0.55*d);a.curveTo(c,e+0.66*d,c+0.18*f,e+0.9*d,c+0.31*f,e+0.8*d);a.curveTo(c+0.4*f,e+d,c+0.7*f,e+d,c+0.8*f,e+0.8*d);a.curveTo(c+f,e+0.8*d,c+f,e+0.6*d,c+0.875*f,e+0.5*d);a.curveTo(c+f,e+0.3*d,c+0.8*f,e+0.1*d,c+0.625*f,e+0.2*
-d);a.curveTo(c+0.5*f,e+0.05*d,c+0.3*f,e+0.05*d,c+0.25*f,e+0.25*d);a.close();return true}a.fillAndStroke()}}};
-mxImageExport.prototype.initMarkers=function(){this.markers=[];var a=function(a,c,d,e,f,g,h,k,i){var l=f*i*1.118,m=g*i*1.118;e.x=e.x-l;e.y=e.y-m;f=f*(h+i);g=g*(h+i);a.begin();a.moveTo(e.x,e.y);a.lineTo(e.x-f-g/2,e.y-g+f/2);d==mxConstants.ARROW_CLASSIC&&a.lineTo(e.x-f*3/4,e.y-g*3/4);a.lineTo(e.x+g/2-f,e.y-g-f/2);a.close();c.style[k?mxConstants.STYLE_STARTFILL:mxConstants.STYLE_ENDFILL]==0?a.stroke():a.fillAndStroke();a=d!=mxConstants.ARROW_CLASSIC?1:0.75;return new mxPoint(-f*a-l,-g*a-m)};this.markers.classic=
-a;this.markers.block=a;this.markers.open=function(a,c,d,e,f,g,h,k,i){c=f*i*1.118;d=g*i*1.118;e.x=e.x-c;e.y=e.y-d;f=f*(h+i);g=g*(h+i);a.begin();a.moveTo(e.x-f-g/2,e.y-g+f/2);a.lineTo(e.x,e.y);a.lineTo(e.x+g/2-f,e.y-g-f/2);a.stroke();return new mxPoint(-c*2,-d*2)};this.markers.oval=function(a,c,d,e,f,g,h,k){d=h/2;a.ellipse(e.x-d,e.y-d,h,h);c.style[k?mxConstants.STYLE_STARTFILL:mxConstants.STYLE_ENDFILL]==0?a.stroke():a.fillAndStroke();return new mxPoint(-f/2,-g/2)};a=function(a,c,d,e,f,g,h,k,i){var l=
-d==mxConstants.ARROW_DIAMOND?0.7071:0.9862,m=f*i*l,l=g*i*l,f=f*(h+i),g=g*(h+i);e.x=e.x-m;e.y=e.y-l;d=d==mxConstants.ARROW_DIAMOND?2:3.4;a.begin();a.moveTo(e.x,e.y);a.lineTo(e.x-f/2-g/d,e.y+f/d-g/2);a.lineTo(e.x-f,e.y-g);a.lineTo(e.x-f/2+g/d,e.y-g/2-f/d);a.close();c.style[k?mxConstants.STYLE_STARTFILL:mxConstants.STYLE_ENDFILL]==0?a.stroke():a.fillAndStroke();return new mxPoint(-m-f,-l-g)};this.markers.diamond=a;this.markers.diamondThin=a};
-var mxXmlCanvas2D=function(a){var b=new mxUrlConverter,c=true,d=true,e=a.ownerDocument,f=[],g={alpha:1,dashed:false,strokewidth:1,fontsize:mxConstants.DEFAULT_FONTSIZE,fontfamily:mxConstants.DEFAULT_FONTFAMILY,fontcolor:"#000000"},h=function(a){return Math.round(parseFloat(a)*100)/100};return{getConverter:function(){return b},isCompressed:function(){return c},setCompressed:function(a){c=a},isTextEnabled:function(){return d},setTextEnabled:function(a){d=a},getDocument:function(){return e},save:function(){if(c){f.push(g);
-g=mxUtils.clone(g)}a.appendChild(e.createElement("save"))},restore:function(){c&&(g=f.pop());a.appendChild(e.createElement("restore"))},scale:function(b){var c=e.createElement("scale");c.setAttribute("scale",b);a.appendChild(c)},translate:function(b,c){var d=e.createElement("translate");d.setAttribute("dx",h(b));d.setAttribute("dy",h(c));a.appendChild(d)},rotate:function(b,c,d,f,g){var o=e.createElement("rotate");o.setAttribute("theta",h(b));o.setAttribute("flipH",c?"1":"0");o.setAttribute("flipV",
-d?"1":"0");o.setAttribute("cx",h(f));o.setAttribute("cy",h(g));a.appendChild(o)},setStrokeWidth:function(b){if(c){if(g.strokewidth==b)return;g.strokewidth=b}var d=e.createElement("strokewidth");d.setAttribute("width",h(b));a.appendChild(d)},setStrokeColor:function(b){var c=e.createElement("strokecolor");c.setAttribute("color",b);a.appendChild(c)},setDashed:function(b){if(c){if(g.dashed==b)return;g.dashed=b}var d=e.createElement("dashed");d.setAttribute("dashed",b?"1":"0");a.appendChild(d)},setDashPattern:function(b){var c=
-e.createElement("dashpattern");c.setAttribute("pattern",b);a.appendChild(c)},setLineCap:function(b){var c=e.createElement("linecap");c.setAttribute("cap",b);a.appendChild(c)},setLineJoin:function(b){var c=e.createElement("linejoin");c.setAttribute("join",b);a.appendChild(c)},setMiterLimit:function(b){var c=e.createElement("miterlimit");c.setAttribute("limit",b);a.appendChild(c)},setFontSize:function(b){if(d){if(c){if(g.fontsize==b)return;g.fontsize=b}var f=e.createElement("fontsize");f.setAttribute("size",
-b);a.appendChild(f)}},setFontColor:function(b){if(d){if(c){if(g.fontcolor==b)return;g.fontcolor=b}var f=e.createElement("fontcolor");f.setAttribute("color",b);a.appendChild(f)}},setFontFamily:function(b){if(d){if(c){if(g.fontfamily==b)return;g.fontfamily=b}var f=e.createElement("fontfamily");f.setAttribute("family",b);a.appendChild(f)}},setFontStyle:function(b){if(d){var c=e.createElement("fontstyle");c.setAttribute("style",b);a.appendChild(c)}},setAlpha:function(b){if(c){if(g.alpha==b)return;g.alpha=
-b}var d=e.createElement("alpha");d.setAttribute("alpha",h(b));a.appendChild(d)},setFillColor:function(b){var c=e.createElement("fillcolor");c.setAttribute("color",b);a.appendChild(c)},setGradient:function(b,c,d,f,g,o,p){var q=e.createElement("gradient");q.setAttribute("c1",b);q.setAttribute("c2",c);q.setAttribute("x",h(d));q.setAttribute("y",h(f));q.setAttribute("w",h(g));q.setAttribute("h",h(o));p!=null&&q.setAttribute("direction",p);a.appendChild(q)},setGlassGradient:function(b,c,d,f){var g=e.createElement("glass");
-g.setAttribute("x",h(b));g.setAttribute("y",h(c));g.setAttribute("w",h(d));g.setAttribute("h",h(f));a.appendChild(g)},rect:function(b,c,d,f){var g=e.createElement("rect");g.setAttribute("x",h(b));g.setAttribute("y",h(c));g.setAttribute("w",h(d));g.setAttribute("h",h(f));a.appendChild(g)},roundrect:function(b,c,d,f,g,o){var p=e.createElement("roundrect");p.setAttribute("x",h(b));p.setAttribute("y",h(c));p.setAttribute("w",h(d));p.setAttribute("h",h(f));p.setAttribute("dx",h(g));p.setAttribute("dy",
-h(o));a.appendChild(p)},ellipse:function(b,c,d,f){var g=e.createElement("ellipse");g.setAttribute("x",h(b));g.setAttribute("y",h(c));g.setAttribute("w",h(d));g.setAttribute("h",h(f));a.appendChild(g)},image:function(c,d,f,g,n,o,p,q){var n=b.convert(n),t=e.createElement("image");t.setAttribute("x",h(c));t.setAttribute("y",h(d));t.setAttribute("w",h(f));t.setAttribute("h",h(g));t.setAttribute("src",n);t.setAttribute("aspect",o?"1":"0");t.setAttribute("flipH",p?"1":"0");t.setAttribute("flipV",q?"1":
-"0");a.appendChild(t)},text:function(b,c,f,g,n,o,p,q,t,u){if(d){var v=e.createElement("text");v.setAttribute("x",h(b));v.setAttribute("y",h(c));v.setAttribute("w",h(f));v.setAttribute("h",h(g));v.setAttribute("str",n);o!=null&&v.setAttribute("align",o);p!=null&&v.setAttribute("valign",p);v.setAttribute("vertical",q?"1":"0");v.setAttribute("wrap",t?"1":"0");v.setAttribute("format",u);a.appendChild(v)}},begin:function(){a.appendChild(e.createElement("begin"))},moveTo:function(b,c){var d=e.createElement("move");
-d.setAttribute("x",h(b));d.setAttribute("y",h(c));a.appendChild(d)},lineTo:function(b,c){var d=e.createElement("line");d.setAttribute("x",h(b));d.setAttribute("y",h(c));a.appendChild(d)},quadTo:function(b,c,d,f){var g=e.createElement("quad");g.setAttribute("x1",h(b));g.setAttribute("y1",h(c));g.setAttribute("x2",h(d));g.setAttribute("y2",h(f));a.appendChild(g)},curveTo:function(b,c,d,f,g,o){var p=e.createElement("curve");p.setAttribute("x1",h(b));p.setAttribute("y1",h(c));p.setAttribute("x2",h(d));
-p.setAttribute("y2",h(f));p.setAttribute("x3",h(g));p.setAttribute("y3",h(o));a.appendChild(p)},close:function(){a.appendChild(e.createElement("close"))},stroke:function(){a.appendChild(e.createElement("stroke"))},fill:function(){a.appendChild(e.createElement("fill"))},fillAndStroke:function(){a.appendChild(e.createElement("fillstroke"))},shadow:function(b,c){var d=e.createElement("shadow");d.setAttribute("value",b);c!=null&&d.setAttribute("filled",c?"1":"0");a.appendChild(d)},clip:function(){a.appendChild(e.createElement("clip"))}}},
-mxSvgCanvas2D=function(a,b){var b=b!=null?b:false,c=new mxUrlConverter,d=true,e=true,f=true,g=function(b,c){var d=a.ownerDocument||document;if(d.createElementNS!=null)return d.createElementNS(c||mxConstants.NS_SVG,b);d=d.createElement(b);c!=null&&d.setAttribute("xmlns",c);return d},h=g("defs");if(b){var k=g("style");k.setAttribute("type","text/css");mxUtils.write(k,"svg{font-family:"+mxConstants.DEFAULT_FONTFAMILY+";font-size:"+mxConstants.DEFAULT_FONTSIZE+";fill:none;stroke-miterlimit:10}");d&&mxUtils.write(k,
-"rect{shape-rendering:crispEdges}");h.appendChild(k)}a.appendChild(h);var i={dx:0,dy:0,scale:1,transform:"",fill:null,gradient:null,stroke:null,strokeWidth:1,dashed:false,dashpattern:"3 3",alpha:1,linecap:"flat",linejoin:"miter",miterlimit:10,fontColor:"#000000",fontSize:mxConstants.DEFAULT_FONTSIZE,fontFamily:mxConstants.DEFAULT_FONTFAMILY,fontStyle:0},l=true,m=null,n=null,o=null,p=null,q=[],t=0,u=[],v=function(a,b){var c="margin:0px;font-size:"+Math.floor(i.fontSize)+"px;font-family:"+i.fontFamily+
-";color:"+i.fontColor+";";(i.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&(c=c+"font-weight:bold;");(i.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&(c=c+"font-style:italic;");(i.fontStyle&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&(c=c+"font-decoration:underline;");b==mxConstants.ALIGN_CENTER?c=c+"text-align:center;":b==mxConstants.ALIGN_RIGHT&&(c=c+"text-align:right;");var d=document.createElement("div");d.innerHTML=a;a=d.innerHTML.replace(/&nbsp;/g,"&#160;");
-return mxUtils.parseXml('<div xmlns="http://www.w3.org/1999/xhtml" style="'+c+'">'+a+"</div>").documentElement},w=function(c,e,f,g){if(c!=null){if(e.clip!=null){c.setAttribute("clip-path","url(#"+e.clip+")");e.clip=null}if(o!=null){c.setAttribute("d",o.join(" "));o=null;if(d&&l){c.setAttribute("shape-rendering","crispEdges");e.strokeWidth=Math.max(1,e.strokeWidth)}}e.alpha<1&&c.setAttribute("opacity",e.alpha);f&&(e.fill!=null||e.gradient!=null)?e.gradient!=null?c.setAttribute("fill","url(#"+e.gradient+
-")"):c.setAttribute("fill",e.fill.toLowerCase()):b||c.setAttribute("fill","none");if(g&&e.stroke!=null){c.setAttribute("stroke",e.stroke.toLowerCase());if(e.strokeWidth!=1){if(c.nodeName=="rect"&&d)e.strokeWidth=Math.max(1,e.strokeWidth);c.setAttribute("stroke-width",e.strokeWidth)}if(c.nodeName=="path"){e.linejoin!=null&&e.linejoin!="miter"&&c.setAttribute("stroke-linejoin",e.linejoin);if(e.linecap!=null){f=e.linecap;f=="flat"&&(f="butt");f!="butt"&&c.setAttribute("stroke-linecap",f)}e.miterlimit!=
-null&&(!b||e.miterlimit!=10)&&c.setAttribute("stroke-miterlimit",e.miterlimit)}if(e.dashed){f=e.dashpattern.split(" ");if(f.length>0){for(var g=[],h=0;h<f.length;h++)g[h]=Number(f[h])*i.strokeWidth;c.setAttribute("stroke-dasharray",g.join(" "))}}}e.transform.length>0&&c.setAttribute("transform",e.transform);a.appendChild(c)}},r=function(a){return Math.round(parseFloat(a)*100)/100};return{getConverter:function(){return c},isAutoAntiAlias:function(){return d},setAutoAntiAlias:function(a){d=a},isTextEnabled:function(){return e},
-setTextEnabled:function(a){e=a},isFoEnabled:function(){return f},setFoEnabled:function(a){f=a},save:function(){u.push(i);i=mxUtils.clone(i)},restore:function(){i=u.pop()},scale:function(a){i.scale=i.scale*a;i.strokeWidth=i.strokeWidth*a},translate:function(a,b){i.dx=i.dx+a;i.dy=i.dy+b},rotate:function(a,b,c,d,e){d=d+i.dx;e=e+i.dy;d=d*i.scale;e=e*i.scale;if(b^c){var f=b?d:0,b=b?-1:1,g=c?e:0,c=c?-1:1;i.transform=i.transform+("translate("+r(f)+","+r(g)+")");i.transform=i.transform+("scale("+r(b)+","+
-r(c)+")");i.transform=i.transform+("translate("+r(-f)+" "+r(-g)+")")}i.transform=i.transform+("rotate("+r(a)+","+r(d)+","+r(e)+")")},setStrokeWidth:function(a){i.strokeWidth=a*i.scale},setStrokeColor:function(a){i.stroke=a},setDashed:function(a){i.dashed=a},setDashPattern:function(a){i.dashpattern=a},setLineCap:function(a){i.linecap=a},setLineJoin:function(a){i.linejoin=a},setMiterLimit:function(a){i.miterlimit=a},setFontSize:function(a){i.fontSize=a},setFontColor:function(a){i.fontColor=a},setFontFamily:function(a){i.fontFamily=
-a},setFontStyle:function(a){i.fontStyle=a},setAlpha:function(a){i.alpha=a},setFillColor:function(a){i.fill=a;i.gradient=null},setGradient:function(a,b,c,d,e,f,k){if(a!=null&&b!=null){c=i;d=a;e=b;d.charAt(0)=="#"&&(d=d.substring(1));e.charAt(0)=="#"&&(e=e.substring(1));d=d.toLowerCase();e=e.toLowerCase();f=null;if(k==null||k==mxConstants.DIRECTION_SOUTH)f="s";else if(k==mxConstants.DIRECTION_EAST)f="e";else{var l=d,d=e,e=l;k==mxConstants.DIRECTION_NORTH?f="s":k==mxConstants.DIRECTION_WEST&&(f="e")}d=
-d+"-"+e+"-"+f;e=q[d];if(e==null){e=g("linearGradient");e.setAttribute("id",++t);e.setAttribute("x1","0%");e.setAttribute("y1","0%");e.setAttribute("x2","0%");e.setAttribute("y2","0%");k==null||k==mxConstants.DIRECTION_SOUTH?e.setAttribute("y2","100%"):k==mxConstants.DIRECTION_EAST?e.setAttribute("x2","100%"):k==mxConstants.DIRECTION_NORTH?e.setAttribute("y1","100%"):k==mxConstants.DIRECTION_WEST&&e.setAttribute("x1","100%");k=g("stop");k.setAttribute("offset","0%");k.setAttribute("style","stop-color:"+
-a);e.appendChild(k);k=g("stop");k.setAttribute("offset","100%");k.setAttribute("style","stop-color:"+b);e.appendChild(k);h.appendChild(e);q[d]=e}b=e.getAttribute("id");c.gradient=b;i.fill=a}},setGlassGradient:function(){if(m==null){m=g("linearGradient");m.setAttribute("id","0");m.setAttribute("x1","0%");m.setAttribute("y1","0%");m.setAttribute("x2","0%");m.setAttribute("y2","100%");var a=g("stop");a.setAttribute("offset","0%");a.setAttribute("style","stop-color:#ffffff;stop-opacity:0.9");m.appendChild(a);
-a=g("stop");a.setAttribute("offset","100%");a.setAttribute("style","stop-color:#ffffff;stop-opacity:0.1");m.appendChild(a);h.firstChild.nextSibling!=null?h.insertBefore(m,h.firstChild.nextSibling):h.appendChild(m)}i.gradient="0"},rect:function(a,c,e,f){a=a+i.dx;c=c+i.dy;n=g("rect");n.setAttribute("x",r(a*i.scale));n.setAttribute("y",r(c*i.scale));n.setAttribute("width",r(e*i.scale));n.setAttribute("height",r(f*i.scale));!b&&d&&n.setAttribute("shape-rendering","crispEdges")},roundrect:function(a,c,
-e,f,h,k){a=a+i.dx;c=c+i.dy;n=g("rect");n.setAttribute("x",r(a*i.scale));n.setAttribute("y",r(c*i.scale));n.setAttribute("width",r(e*i.scale));n.setAttribute("height",r(f*i.scale));h>0&&n.setAttribute("rx",r(h*i.scale));k>0&&n.setAttribute("ry",r(k*i.scale));!b&&d&&n.setAttribute("shape-rendering","crispEdges")},ellipse:function(a,b,c,d){a=a+i.dx;b=b+i.dy;n=g("ellipse");n.setAttribute("cx",r((a+c/2)*i.scale));n.setAttribute("cy",r((b+d/2)*i.scale));n.setAttribute("rx",r(c/2*i.scale));n.setAttribute("ry",
-r(d/2*i.scale))},image:function(b,d,e,f,h,k,l,m){var h=c.convert(h),k=k!=null?k:true,l=l!=null?l:false,m=m!=null?m:false,b=b+i.dx,d=d+i.dy,n=g("image");n.setAttribute("x",r(b*i.scale));n.setAttribute("y",r(d*i.scale));n.setAttribute("width",r(e*i.scale));n.setAttribute("height",r(f*i.scale));mxClient.IS_VML?n.setAttribute("xlink:href",h):n.setAttributeNS(mxConstants.NS_XLINK,"xlink:href",h);k||n.setAttribute("preserveAspectRatio","none");i.alpha<1&&n.setAttribute("opacity",i.alpha);h=i.transform;
-if(l||m){var o=k=1,p=0,q=0;if(l){k=-1;p=-e-2*b}if(m){o=-1;q=-f-2*d}h=h+("scale("+k+","+o+")translate("+p+","+q+")")}h.length>0&&n.setAttribute("transform",h);a.appendChild(n)},text:function(c,d,h,k,l,m,n,o,p,q){if(e){c=c+i.dx;d=d+i.dy;if(f&&q=="html"){p=g("g");p.setAttribute("transform",i.transform+"scale("+i.scale+","+i.scale+")");i.alpha<1&&p.setAttribute("opacity",i.alpha);q=g("foreignObject");q.setAttribute("x",Math.round(c));q.setAttribute("y",Math.round(d));q.setAttribute("width",Math.round(h));
-q.setAttribute("height",Math.round(k));q.appendChild(v(l,m,n));p.appendChild(q)}else{var q=Math.floor(i.fontSize),p=g("g"),t=i.transform;if(o){o=d+k/2;t=t+("rotate(-90,"+r((c+h/2)*i.scale)+","+r(o*i.scale)+")")}t.length>0&&p.setAttribute("transform",t);i.alpha<1&&p.setAttribute("opacity",i.alpha);m=m==mxConstants.ALIGN_RIGHT?"end":m==mxConstants.ALIGN_CENTER?"middle":"start";c=m=="end"?c+Math.max(0,h-2):m=="middle"?c+h/2:c+(h>0?2:0);(i.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&p.setAttribute("font-weight",
-"bold");(i.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&p.setAttribute("font-style","italic");(i.fontStyle&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&p.setAttribute("text-decoration","underline");m!="start"&&p.setAttribute("text-anchor",m);(!b||q!=mxConstants.DEFAULT_FONTSIZE)&&p.setAttribute("font-size",Math.floor(q*i.scale)+"px");(!b||i.fontFamily!=mxConstants.DEFAULT_FONTFAMILY)&&p.setAttribute("font-family",i.fontFamily);p.setAttribute("fill",i.fontColor);h=l.split("\n");
-l=q*1.25;m=k-(k>0?q+(h.length-1)*l:h.length*l-1);n==null||n==mxConstants.ALIGN_TOP?d=Math.max(d-3*i.scale,d+m/2+(k>0?l/2-8:0)):n==mxConstants.ALIGN_MIDDLE?d=d+m/2:n==mxConstants.ALIGN_BOTTOM&&(d=Math.min(d,d+m+2*i.scale));d=d+q;for(k=0;k<h.length;k++){n=g("text");n.setAttribute("x",r(c*i.scale));n.setAttribute("y",r(d*i.scale));mxUtils.write(n,h[k]);p.appendChild(n);d=d+q*1.3}}a.appendChild(p)}},begin:function(){n=g("path");o=[];p=null;l=true},moveTo:function(a,b){if(o!=null){a=a+i.dx;b=b+i.dy;o.push("M "+
-r(a*i.scale)+" "+r(b*i.scale));d&&(p=new mxPoint(a,b))}},lineTo:function(a,b){if(o!=null){a=a+i.dx;b=b+i.dy;o.push("L "+r(a*i.scale)+" "+r(b*i.scale));if(d){p!=null&&(l&&a!=p.x&&b!=p.y)&&(l=false);p=new mxPoint(a,b)}}},quadTo:function(a,b,c,d){if(o!=null){a=a+i.dx;b=b+i.dy;c=c+i.dx;d=d+i.dy;o.push("Q "+r(a*i.scale)+" "+r(b*i.scale)+" "+r(c*i.scale)+" "+r(d*i.scale));l=false}},curveTo:function(a,b,c,d,e,f){if(o!=null){a=a+i.dx;b=b+i.dy;c=c+i.dx;d=d+i.dy;e=e+i.dx;f=f+i.dy;o.push("C "+r(a*i.scale)+" "+
-r(b*i.scale)+" "+r(c*i.scale)+" "+r(d*i.scale)+" "+r(e*i.scale)+" "+r(f*i.scale));l=false}},close:function(){o!=null&&o.push("Z")},stroke:function(){w(n,i,false,true)},fill:function(){w(n,i,true,false)},fillAndStroke:function(){w(n,i,true,true)},shadow:function(a,b){this.save();this.setStrokeColor(a);if(b){this.setFillColor(a);this.fillAndStroke()}else this.stroke();this.restore()},clip:function(){if(n!=null){if(o!=null){n.setAttribute("d",o.join(" "));o=null}var a=++t,b=g("clipPath");b.setAttribute("id",
-a);b.appendChild(n);h.appendChild(b);i.clip=a}}}};function mxGuide(a,b){this.graph=a;this.setStates(b)}mxGuide.prototype.graph=null;mxGuide.prototype.states=null;mxGuide.prototype.horizontal=!0;mxGuide.prototype.vertical=!0;mxGuide.prototype.guideX=null;mxGuide.prototype.guideY=null;mxGuide.prototype.crisp=!0;mxGuide.prototype.setStates=function(a){this.states=a};mxGuide.prototype.isEnabledForEvent=function(){return true};
-mxGuide.prototype.getGuideTolerance=function(){return this.graph.gridSize*this.graph.view.scale/2};mxGuide.prototype.createGuideShape=function(){var a=new mxPolyline([],mxConstants.GUIDE_COLOR,mxConstants.GUIDE_STROKEWIDTH);a.crisp=this.crisp;a.isDashed=true;return a};
-mxGuide.prototype.move=function(a,b,c){if(this.states!=null&&(this.horizontal||this.vertical)&&a!=null&&b!=null){var d=this.graph.getView().translate,e=this.graph.getView().scale,f=b.x,g=b.y,h=false,k=false,i=this.getGuideTolerance(),l=i,m=i,i=a.clone();i.x=i.x+b.x;i.y=i.y+b.y;for(var n=i.x,o=i.x+i.width,p=i.getCenterX(),q=i.y,t=i.y+i.height,u=i.getCenterY(),b=function(b){var b=b+this.graph.panDx,c=false;if(Math.abs(b-p)<l){f=b-a.getCenterX();l=Math.abs(b-p);c=true}else if(Math.abs(b-n)<l){f=b-a.x;
-l=Math.abs(b-n);c=true}else if(Math.abs(b-o)<l){f=b-a.x-a.width;l=Math.abs(b-o);c=true}if(c){if(this.guideX==null){this.guideX=this.createGuideShape(true);this.guideX.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;this.guideX.init(this.graph.getView().getOverlayPane());if(this.graph.dialect==mxConstants.DIALECT_SVG){this.guideX.node.setAttribute("pointer-events","none");this.guideX.pipe.setAttribute("pointer-events","none")}}var d=this.graph.container,
-b=b-this.graph.panDx;this.guideX.points=[new mxPoint(b,-this.graph.panDy),new mxPoint(b,d.scrollHeight-3-this.graph.panDy)]}h=h||c},i=function(b){var b=b+this.graph.panDy,c=false;if(Math.abs(b-u)<m){g=b-a.getCenterY();m=Math.abs(b-u);c=true}else if(Math.abs(b-q)<m){g=b-a.y;m=Math.abs(b-q);c=true}else if(Math.abs(b-t)<m){g=b-a.y-a.height;m=Math.abs(b-t);c=true}if(c){if(this.guideY==null){this.guideY=this.createGuideShape(false);this.guideY.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:
-mxConstants.DIALECT_SVG;this.guideY.init(this.graph.getView().getOverlayPane());if(this.graph.dialect==mxConstants.DIALECT_SVG){this.guideY.node.setAttribute("pointer-events","none");this.guideY.pipe.setAttribute("pointer-events","none")}}var d=this.graph.container,b=b-this.graph.panDy;this.guideY.points=[new mxPoint(-this.graph.panDx,b),new mxPoint(d.scrollWidth-3-this.graph.panDx,b)]}k=k||c},v=0;v<this.states.length;v++){var w=this.states[v];if(w!=null){if(this.horizontal){b.call(this,w.getCenterX());
-b.call(this,w.x);b.call(this,w.x+w.width)}if(this.vertical){i.call(this,w.getCenterY());i.call(this,w.y);i.call(this,w.y+w.height)}}}if(!h&&this.guideX!=null)this.guideX.node.style.visibility="hidden";else if(this.guideX!=null){this.guideX.node.style.visibility="visible";this.guideX.redraw()}if(!k&&this.guideY!=null)this.guideY.node.style.visibility="hidden";else if(this.guideY!=null){this.guideY.node.style.visibility="visible";this.guideY.redraw()}if(c){if(!h){c=a.x-(this.graph.snap(a.x/e-d.x)+d.x)*
-e;f=this.graph.snap(f/e)*e-c}if(!k){d=a.y-(this.graph.snap(a.y/e-d.y)+d.y)*e;g=this.graph.snap(g/e)*e-d}}b=new mxPoint(f,g)}return b};mxGuide.prototype.hide=function(){if(this.guideX!=null)this.guideX.node.style.visibility="hidden";if(this.guideY!=null)this.guideY.node.style.visibility="hidden"};mxGuide.prototype.destroy=function(){if(this.guideX!=null){this.guideX.destroy();this.guideX=null}if(this.guideY!=null){this.guideY.destroy();this.guideY=null}};function mxShape(){}
-mxShape.prototype.SVG_STROKE_TOLERANCE=8;mxShape.prototype.scale=1;mxShape.prototype.dialect=null;mxShape.prototype.crisp=!1;mxShape.prototype.roundedCrispSvg=!0;mxShape.prototype.mixedModeHtml=!0;mxShape.prototype.preferModeHtml=!0;mxShape.prototype.bounds=null;mxShape.prototype.points=null;mxShape.prototype.node=null;mxShape.prototype.label=null;mxShape.prototype.innerNode=null;mxShape.prototype.style=null;mxShape.prototype.startOffset=null;mxShape.prototype.endOffset=null;
-mxShape.prototype.boundingBox=null;mxShape.prototype.vmlNodes=["node","strokeNode","fillNode","shadowNode"];mxShape.prototype.vmlScale=1;mxShape.prototype.strokewidth=1;mxShape.prototype.setCursor=function(a){a==null&&(a="");this.cursor=a;if(this.innerNode!=null)this.innerNode.style.cursor=a;if(this.node!=null)this.node.style.cursor=a;if(this.pipe!=null)this.pipe.style.cursor=a};mxShape.prototype.getCursor=function(){return this.cursor};
-mxShape.prototype.init=function(a){if(this.node==null){this.node=this.create(a);if(a!=null){a.appendChild(this.node);document.documentMode==8&&mxUtils.isVml(this.node)&&this.reparseVml()}}if(this.insertGradientNode!=null){this.insertGradient(this.insertGradientNode);this.insertGradientNode=null}};
-mxShape.prototype.reparseVml=function(){for(var a=0;a<this.vmlNodes.length;a++)this[this.vmlNodes[a]]!=null&&this[this.vmlNodes[a]].setAttribute("id","mxTemporaryReference-"+this.vmlNodes[a]);this.node.outerHTML=this.node.outerHTML;for(a=0;a<this.vmlNodes.length;a++)if(this[this.vmlNodes[a]]!=null){this[this.vmlNodes[a]]=this.node.ownerDocument.getElementById("mxTemporaryReference-"+this.vmlNodes[a]);this[this.vmlNodes[a]].removeAttribute("id")}};
-mxShape.prototype.insertGradient=function(a){if(a!=null){for(var b=0,c=a.getAttribute("id"),d=document.getElementById(c);d!=null&&d.ownerSVGElement!=this.node.ownerSVGElement;){b++;c=a.getAttribute("id")+"-"+b;d=document.getElementById(c)}if(d==null){a.setAttribute("id",c);this.node.ownerSVGElement.appendChild(a);d=a}if(d!=null){a="url(#"+c+")";b=this.innerNode!=null?this.innerNode:this.node;b!=null&&b.getAttribute("fill")!=a&&b.setAttribute("fill",a)}}};
-mxShape.prototype.isMixedModeHtml=function(){return this.mixedModeHtml&&!this.isRounded&&!this.isShadow&&this.gradient==null&&mxUtils.getValue(this.style,mxConstants.STYLE_GLASS,0)==0&&mxUtils.getValue(this.style,mxConstants.STYLE_ROTATION,0)==0};
-mxShape.prototype.create=function(){var a=null;return a=this.dialect==mxConstants.DIALECT_SVG?this.createSvg():this.dialect==mxConstants.DIALECT_STRICTHTML||this.preferModeHtml&&this.dialect==mxConstants.DIALECT_PREFERHTML||this.isMixedModeHtml()&&this.dialect==mxConstants.DIALECT_MIXEDHTML?this.createHtml():this.createVml()};mxShape.prototype.createHtml=function(){var a=document.createElement("DIV");this.configureHtmlShape(a);return a};
-mxShape.prototype.destroy=function(){if(this.node!=null){mxEvent.release(this.node);this.node.parentNode!=null&&this.node.parentNode.removeChild(this.node);if(this.node.glassOverlay){this.node.glassOverlay.parentNode.removeChild(this.node.glassOverlay);this.node.glassOverlay=null}this.node=null}};
-mxShape.prototype.apply=function(a){this.style=a=a.style;if(a!=null){this.fill=mxUtils.getValue(a,mxConstants.STYLE_FILLCOLOR,this.fill);this.gradient=mxUtils.getValue(a,mxConstants.STYLE_GRADIENTCOLOR,this.gradient);this.gradientDirection=mxUtils.getValue(a,mxConstants.STYLE_GRADIENT_DIRECTION,this.gradientDirection);this.opacity=mxUtils.getValue(a,mxConstants.STYLE_OPACITY,this.opacity);this.stroke=mxUtils.getValue(a,mxConstants.STYLE_STROKECOLOR,this.stroke);this.strokewidth=mxUtils.getNumber(a,
-mxConstants.STYLE_STROKEWIDTH,this.strokewidth);this.isShadow=mxUtils.getValue(a,mxConstants.STYLE_SHADOW,this.isShadow);this.isDashed=mxUtils.getValue(a,mxConstants.STYLE_DASHED,this.isDashed);this.spacing=mxUtils.getValue(a,mxConstants.STYLE_SPACING,this.spacing);this.startSize=mxUtils.getNumber(a,mxConstants.STYLE_STARTSIZE,this.startSize);this.endSize=mxUtils.getNumber(a,mxConstants.STYLE_ENDSIZE,this.endSize);this.isRounded=mxUtils.getValue(a,mxConstants.STYLE_ROUNDED,this.isRounded);this.startArrow=
-mxUtils.getValue(a,mxConstants.STYLE_STARTARROW,this.startArrow);this.endArrow=mxUtils.getValue(a,mxConstants.STYLE_ENDARROW,this.endArrow);this.rotation=mxUtils.getValue(a,mxConstants.STYLE_ROTATION,this.rotation);this.direction=mxUtils.getValue(a,mxConstants.STYLE_DIRECTION,this.direction);if(this.fill=="none")this.fill=null;if(this.gradient=="none")this.gradient=null;if(this.stroke=="none")this.stroke=null}};
-mxShape.prototype.createSvgGroup=function(a){var b=document.createElementNS(mxConstants.NS_SVG,"g");this.innerNode=document.createElementNS(mxConstants.NS_SVG,a);this.configureSvgShape(this.innerNode);a=="rect"&&(this.strokewidth*this.scale>=1&&!this.isRounded)&&this.innerNode.setAttribute("shape-rendering","optimizeSpeed");this.shadowNode=this.createSvgShadow(this.innerNode);this.shadowNode!=null&&b.appendChild(this.shadowNode);b.appendChild(this.innerNode);return b};
-mxShape.prototype.createSvgShadow=function(a){if(this.isShadow){a=a.cloneNode(true);a.setAttribute("opacity",mxConstants.SHADOW_OPACITY);this.fill!=null&&this.fill!=mxConstants.NONE&&a.setAttribute("fill",mxConstants.SHADOWCOLOR);this.stroke!=null&&this.stroke!=mxConstants.NONE&&a.setAttribute("stroke",mxConstants.SHADOWCOLOR);return a}return null};
-mxShape.prototype.configureHtmlShape=function(a){if(mxUtils.isVml(a))this.configureVmlShape(a);else{a.style.position="absolute";a.style.overflow="hidden";var b=this.stroke;if(b!=null&&b!=mxConstants.NONE){a.style.borderColor=b;if(this.isDashed)a.style.borderStyle="dashed";else if(this.strokewidth>0)a.style.borderStyle="solid";a.style.borderWidth=Math.ceil(this.strokewidth*this.scale)+"px"}else a.style.borderWidth="0px";b=this.fill;a.style.background="";b!=null&&b!=mxConstants.NONE?a.style.backgroundColor=
-b:this.points==null&&this.configureTransparentBackground(a);this.opacity!=null&&mxUtils.setOpacity(a,this.opacity)}};mxShape.prototype.updateVmlFill=function(a,b,c,d,e){a.color=b;if(e!=null&&e!=100){a.opacity=e+"%";c!=null&&a.setAttribute("o:opacity2",e+"%")}if(c!=null){a.type="gradient";a.color2=c;b="180";this.gradientDirection==mxConstants.DIRECTION_EAST?b="270":this.gradientDirection==mxConstants.DIRECTION_WEST?b="90":this.gradientDirection==mxConstants.DIRECTION_NORTH&&(b="0");a.angle=b}};
-mxShape.prototype.updateVmlStrokeNode=function(a){if(this.strokeNode==null){this.strokeNode=document.createElement("v:stroke");this.strokeNode.joinstyle="miter";this.strokeNode.miterlimit=4;a.appendChild(this.strokeNode)}if(this.opacity!=null)this.strokeNode.opacity=this.opacity+"%";this.updateVmlDashStyle()};mxShape.prototype.updateVmlStrokeColor=function(a){var b=this.stroke;if(b!=null&&b!=mxConstants.NONE){a.stroked="true";a.strokecolor=b}else a.stroked="false"};
-mxShape.prototype.configureVmlShape=function(a){a.style.position="absolute";this.updateVmlStrokeColor(a);a.style.background="";var b=this.fill;if(b!=null&&b!=mxConstants.NONE){if(this.fillNode==null){this.fillNode=document.createElement("v:fill");a.appendChild(this.fillNode)}this.updateVmlFill(this.fillNode,b,this.gradient,this.gradientDirection,this.opacity)}else{a.filled="false";this.points==null&&this.configureTransparentBackground(a)}this.updateVmlStrokeNode(a);this.isShadow&&this.createVmlShadow(a);
-if(a.nodeName=="roundrect")try{var c=mxConstants.RECTANGLE_ROUNDING_FACTOR*100;this.style!=null&&(c=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,c));a.setAttribute("arcsize",""+c+"%")}catch(d){}};
-mxShape.prototype.createVmlShadow=function(a){if(this.shadowNode==null){this.shadowNode=document.createElement("v:shadow");this.shadowNode.on="true";this.shadowNode.color=mxConstants.SHADOWCOLOR;this.shadowNode.opacity=mxConstants.SHADOW_OPACITY*100+"%";this.shadowStrokeNode=document.createElement("v:stroke");this.shadowNode.appendChild(this.shadowStrokeNode);a.appendChild(this.shadowNode)}};
-mxShape.prototype.configureTransparentBackground=function(a){a.style.background="url('"+mxClient.imageBasePath+"/transparent.gif')"};
-mxShape.prototype.configureSvgShape=function(a){var b=this.stroke;b!=null&&b!=mxConstants.NONE?a.setAttribute("stroke",b):a.setAttribute("stroke","none");b=this.fill;if(b!=null&&b!=mxConstants.NONE)if(this.gradient!=null){var c=this.getGradientId(b,this.gradient);if(this.gradientNode!=null&&this.gradientNode.getAttribute("id")!=c){this.gradientNode=null;a.setAttribute("fill","")}if(this.gradientNode==null){this.gradientNode=this.createSvgGradient(c,b,this.gradient,a);a.setAttribute("fill","url(#"+
-c+")")}}else{this.gradientNode=null;a.setAttribute("fill",b)}else a.setAttribute("fill","none");if(this.opacity!=null){a.setAttribute("fill-opacity",this.opacity/100);a.setAttribute("stroke-opacity",this.opacity/100)}};
-mxShape.prototype.getGradientId=function(a,b){a.charAt(0)=="#"&&(a=a.substring(1));b.charAt(0)=="#"&&(b=b.substring(1));var a=a.toLowerCase(),b=b.toLowerCase(),c=null;if(this.gradientDirection==null||this.gradientDirection==mxConstants.DIRECTION_SOUTH)c="south";else if(this.gradientDirection==mxConstants.DIRECTION_EAST)c="east";else{var d=a,a=b,b=d;this.gradientDirection==mxConstants.DIRECTION_NORTH?c="south":this.gradientDirection==mxConstants.DIRECTION_WEST&&(c="east")}return"mx-gradient-"+a+"-"+
-b+"-"+c};mxShape.prototype.createSvgPipe=function(){var a=document.createElementNS(mxConstants.NS_SVG,"path");a.setAttribute("pointer-events","stroke");a.setAttribute("fill","none");a.setAttribute("visibility","hidden");a.setAttribute("stroke",mxClient.IS_OP?"none":"white");return a};
-mxShape.prototype.createSvgGradient=function(a,b,c){var d=this.insertGradientNode;if(d==null){d=document.createElementNS(mxConstants.NS_SVG,"linearGradient");d.setAttribute("id",a);d.setAttribute("x1","0%");d.setAttribute("y1","0%");d.setAttribute("x2","0%");d.setAttribute("y2","0%");this.gradientDirection==null||this.gradientDirection==mxConstants.DIRECTION_SOUTH?d.setAttribute("y2","100%"):this.gradientDirection==mxConstants.DIRECTION_EAST?d.setAttribute("x2","100%"):this.gradientDirection==mxConstants.DIRECTION_NORTH?
-d.setAttribute("y1","100%"):this.gradientDirection==mxConstants.DIRECTION_WEST&&d.setAttribute("x1","100%");a=document.createElementNS(mxConstants.NS_SVG,"stop");a.setAttribute("offset","0%");a.setAttribute("style","stop-color:"+b);d.appendChild(a);a=document.createElementNS(mxConstants.NS_SVG,"stop");a.setAttribute("offset","100%");a.setAttribute("style","stop-color:"+c);d.appendChild(a)}return this.insertGradientNode=d};
-mxShape.prototype.createPoints=function(a,b,c,d){var e=d?this.bounds.x:0,d=d?this.bounds.y:0,f=this.crisp&&this.dialect==mxConstants.DIALECT_SVG&&mxClient.IS_IE?0.5:0;if(isNaN(this.points[0].x)||isNaN(this.points[0].y))return null;var g=mxConstants.LINE_ARCSIZE*this.scale,h=this.points[0];if(this.startOffset!=null){h=h.clone();h.x=h.x+this.startOffset.x;h.y=h.y+this.startOffset.y}for(var a=a+" "+(Math.round(h.x-e)+f)+" "+(Math.round(h.y-d)+f)+" ",k=1;k<this.points.length;k++){var h=this.points[k-
-1],i=this.points[k];if(isNaN(i.x)||isNaN(i.y))return null;if(k==this.points.length-1&&this.endOffset!=null){i=i.clone();i.x=i.x+this.endOffset.x;i.y=i.y+this.endOffset.y}var l=h.x-i.x,h=h.y-i.y;if(this.isRounded&&k<this.points.length-1&&(l!=0||h!=0)&&this.scale>0.3){var m=Math.sqrt(l*l+h*h),l=l*Math.min(g,m/2)/m,h=h*Math.min(g,m/2)/m,a=a+(b+" "+(Math.round(i.x+l-e)+f)+" "+(Math.round(i.y+h-d)+f)+" "),h=this.points[k+1],l=h.x-i.x,h=h.y-i.y,m=Math.max(1,Math.sqrt(l*l+h*h));if(m!=0){l=l*Math.min(g,m/
-2)/m;h=h*Math.min(g,m/2)/m;a=a+(c+" "+Math.round(i.x-e)+" "+Math.round(i.y-d)+" "+Math.round(i.x-e)+","+Math.round(i.y-d)+" "+(Math.round(i.x+l-e)+f)+" "+(Math.round(i.y+h-d)+f)+" ")}}else a=a+(b+" "+(Math.round(i.x-e)+f)+" "+(Math.round(i.y-d)+f)+" ")}return a};
-mxShape.prototype.updateHtmlShape=function(a){if(a!=null){if(mxUtils.isVml(a))this.updateVmlShape(a);else{var b=Math.ceil(this.strokewidth*this.scale);a.style.borderWidth=Math.max(1,b)+"px";if(this.bounds!=null&&!isNaN(this.bounds.x)&&!isNaN(this.bounds.y)&&!isNaN(this.bounds.width)&&!isNaN(this.bounds.height)){a.style.left=Math.round(this.bounds.x-b/2)+"px";a.style.top=Math.round(this.bounds.y-b/2)+"px";document.compatMode=="CSS1Compat"&&(b=-b);a.style.width=Math.round(Math.max(0,this.bounds.width+
-b))+"px";a.style.height=Math.round(Math.max(0,this.bounds.height+b))+"px";a.style.visibility=this.bounds.width==0||this.bounds.height==0?"hidden":"visible"}}if(this.points!=null&&this.bounds!=null&&!mxUtils.isVml(a)){if(this.divContainer==null)this.divContainer=a;for(;this.divContainer.firstChild!=null;){mxEvent.release(this.divContainer.firstChild);this.divContainer.removeChild(this.divContainer.firstChild)}a.style.borderStyle="";a.style.background="";if(this.points.length==2){var c=this.points[0],
-b=this.points[1],d=b.x-c.x,e=b.y-c.y;if(d==0||e==0)a.style.borderStyle="solid";else{a.style.width=Math.round(this.bounds.width+1)+"px";a.style.height=Math.round(this.bounds.height+1)+"px";for(var b=1+Math.sqrt(d*d+e*e)/(8*this.scale),f=d/b,g=e/b,a=c.x-this.bounds.x,c=c.y-this.bounds.y,d=0;d<b;d++){e=document.createElement("DIV");e.style.position="absolute";e.style.overflow="hidden";e.style.left=Math.round(a)+"px";e.style.top=Math.round(c)+"px";e.style.width=Math.max(1,2*this.scale)+"px";e.style.height=
-Math.max(1,2*this.scale)+"px";e.style.backgroundColor=this.stroke;this.divContainer.appendChild(e);a=a+f;c=c+g}}}else if(this.points.length==3){c=this.points[1];d="0";e="1";b="0";f="1";if(c.x==this.bounds.x){f="0";b="1"}if(c.y==this.bounds.y){d="1";e="0"}a.style.borderStyle="solid";a.style.borderWidth=d+" "+f+" "+e+" "+b+"px"}else{a.style.width=Math.round(this.bounds.width+1)+"px";a.style.height=Math.round(this.bounds.height+1)+"px";g=this.points[0];for(d=1;d<this.points.length;d++){f=this.points[d];
-e=document.createElement("DIV");e.style.position="absolute";e.style.overflow="hidden";e.style.borderColor=this.stroke;e.style.borderStyle="solid";e.style.borderWidth="1 0 0 1px";a=Math.min(f.x,g.x)-this.bounds.x;c=Math.min(f.y,g.y)-this.bounds.y;b=Math.max(1,Math.abs(f.x-g.x));g=Math.max(1,Math.abs(f.y-g.y));e.style.left=a+"px";e.style.top=c+"px";e.style.width=b+"px";e.style.height=g+"px";this.divContainer.appendChild(e);g=f}}}}};
-mxShape.prototype.updateVmlDashStyle=function(){if(this.isDashed){if(this.strokeNode.dashstyle!="dash")this.strokeNode.dashstyle="dash"}else if(this.strokeNode.dashstyle!="solid")this.strokeNode.dashstyle="solid"};
-mxShape.prototype.updateVmlShape=function(a){a.strokeweight=this.strokewidth*this.scale+"px";this.strokeNode!=null&&this.updateVmlDashStyle();if(this.shadowNode!=null){var b=Math.round(mxConstants.SHADOW_OFFSET_X*this.scale),c=Math.round(mxConstants.SHADOW_OFFSET_Y*this.scale);this.shadowNode.offset=b+"px,"+c+"px"}if(this.bounds!=null&&!isNaN(this.bounds.x)&&!isNaN(this.bounds.y)&&!isNaN(this.bounds.width)&&!isNaN(this.bounds.height)){var b=1,c=Math.max(0,Math.round(this.bounds.width)),d=Math.max(0,
-Math.round(this.bounds.height));if(this.points!=null||a.nodeName=="shape"||a.nodeName=="group"){var e=a.parentNode.nodeName=="group"?1:this.vmlScale;a.coordsize=c*e+","+d*e}else if(a.parentNode.nodeName=="group")b=this.vmlScale;if(a.parentNode!=this.node){a.style.left=Math.round(this.bounds.x*b)+"px";a.style.top=Math.round(this.bounds.y*b)+"px";if(this.points==null)if(this.rotation!=null&&this.rotation!=0)a.style.rotation=this.rotation;else if(a.style.rotation!=null)a.style.rotation=""}a.style.width=
-c*b+"px";a.style.height=d*b+"px"}if(this.points!=null&&a.nodeName!="group")if(a.nodeName=="polyline"&&a.points!=null){b="";for(c=0;c<this.points.length;c++)b=b+(this.points[c].x+","+this.points[c].y+" ");a.points.value=b;a.style.left=null;a.style.top=null;a.style.width=null;a.style.height=null}else if(this.bounds!=null){b=this.createPoints("m","l","c",true);if(this.style!=null&&this.style[mxConstants.STYLE_SMOOTH]){d=this.points;e=d.length;if(e>3){for(var f=this.bounds.x,g=this.bounds.y,b="m "+Math.round(d[0].x-
-f)+" "+Math.round(d[0].y-g)+" qb",c=1;c<e-1;c++)b=b+(" "+Math.round(d[c].x-f)+" "+Math.round(d[c].y-g));b=b+(" nf l "+Math.round(d[e-1].x-f)+" "+Math.round(d[e-1].y-g))}}a.path=b+" e"}};
-mxShape.prototype.updateSvgBounds=function(a){var b=this.bounds.width,c=this.bounds.height;if(this.isRounded&&(!this.crisp||!mxClient.IS_IE)){a.setAttribute("x",this.bounds.x);a.setAttribute("y",this.bounds.y)}else{var d=this.crisp&&mxClient.IS_IE?0.5:0;a.setAttribute("x",Math.round(this.bounds.x)+d);a.setAttribute("y",Math.round(this.bounds.y)+d);b=Math.round(b);c=Math.round(c)}a.setAttribute("width",b);a.setAttribute("height",c);if(this.isRounded){d=mxConstants.RECTANGLE_ROUNDING_FACTOR*100;this.style!=
-null&&(d=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,d)/100);b=Math.min(b*d,c*d);a.setAttribute("rx",b);a.setAttribute("ry",b)}this.updateSvgTransform(a,a==this.shadowNode)};
-mxShape.prototype.updateSvgPath=function(a){var b=this.createPoints("M","L","C",false);if(b!=null){a.setAttribute("d",b);if(this.style!=null&&this.style[mxConstants.STYLE_SMOOTH]){var b=this.points,c=b.length;if(c>3){for(var d="M "+b[0].x+" "+b[0].y+" ",d=d+(" Q "+b[1].x+" "+b[1].y+" "+b[2].x+" "+b[2].y),e=3;e<c;e++)d=d+(" T "+b[e].x+" "+b[e].y);a.setAttribute("d",d)}}a.removeAttribute("x");a.removeAttribute("y");a.removeAttribute("width");a.removeAttribute("height")}};
-mxShape.prototype.updateSvgScale=function(a){a.setAttribute("stroke-width",Math.round(Math.max(1,this.strokewidth*this.scale)));if(this.isDashed){var b=Math.max(1,Math.round(3*this.scale*this.strokewidth));a.setAttribute("stroke-dasharray",b+" "+b)}this.crisp&&(this.roundedCrispSvg||this.isRounded!=true)&&(this.rotation==null||this.rotation==0)?a.setAttribute("shape-rendering","crispEdges"):a.removeAttribute("shape-rendering")};
-mxShape.prototype.updateSvgShape=function(a){this.points!=null&&this.points[0]!=null?this.updateSvgPath(a):this.bounds!=null&&this.updateSvgBounds(a);this.updateSvgScale(a)};mxShape.prototype.getSvgShadowTransform=function(){return"translate("+mxConstants.SHADOW_OFFSET_X*this.scale+" "+mxConstants.SHADOW_OFFSET_Y*this.scale+")"};
-mxShape.prototype.updateSvgTransform=function(a,b){var c=b?this.getSvgShadowTransform():"";this.rotation!=null&&this.rotation!=0?a.setAttribute("transform","rotate("+this.rotation+","+(this.bounds.x+this.bounds.width/2)+","+(this.bounds.y+this.bounds.height/2)+") "+c):b?a.setAttribute("transform",c):a.removeAttribute("transform")};
-mxShape.prototype.reconfigure=function(){if(this.dialect==mxConstants.DIALECT_SVG){this.innerNode!=null?this.configureSvgShape(this.innerNode):this.configureSvgShape(this.node);if(this.insertGradientNode!=null){this.insertGradient(this.insertGradientNode);this.insertGradientNode=null}}else{if(mxUtils.isVml(this.node)){this.node.style.visibility="hidden";this.configureVmlShape(this.node)}else{this.node.style.visibility="hidden";this.configureHtmlShape(this.node)}this.node.style.visibility="visible"}};
-mxShape.prototype.redraw=function(){this.updateBoundingBox();if(this.dialect==mxConstants.DIALECT_SVG)this.redrawSvg();else if(mxUtils.isVml(this.node)){this.node.style.visibility="hidden";this.redrawVml();this.node.style.visibility="visible"}else this.redrawHtml()};
-mxShape.prototype.updateBoundingBox=function(){if(this.bounds!=null){var a=this.createBoundingBox();this.augmentBoundingBox(a);var b=Number(mxUtils.getValue(this.style,mxConstants.STYLE_ROTATION,0));b!=0&&(a=mxUtils.getBoundingBox(a,b));a.x=Math.floor(a.x);a.y=Math.floor(a.y);a.width=Math.ceil(a.width);a.height=Math.ceil(a.height);this.boundingBox=a}};mxShape.prototype.createBoundingBox=function(){return this.bounds.clone()};
-mxShape.prototype.augmentBoundingBox=function(a){if(this.isShadow){a.width=a.width+Math.ceil(mxConstants.SHADOW_OFFSET_X*this.scale);a.height=a.height+Math.ceil(mxConstants.SHADOW_OFFSET_Y*this.scale)}var b=Math.ceil(this.strokewidth*this.scale);a.grow(Math.ceil(b/2))};
-mxShape.prototype.redrawSvg=function(){if(this.innerNode!=null){this.updateSvgShape(this.innerNode);this.shadowNode!=null&&this.updateSvgShape(this.shadowNode)}else{this.updateSvgShape(this.node);this.shadowNode!=null&&this.shadowNode.setAttribute("transform",this.getSvgShadowTransform())}this.updateSvgGlassPane()};
-mxShape.prototype.updateVmlGlassPane=function(){if(this.bounds!=null&&this.node.nodeName=="group"&&this.style!=null&&mxUtils.getValue(this.style,mxConstants.STYLE_GLASS,0)==1){if(this.node.glassOverlay==null){this.node.glassOverlay=document.createElement("v:shape");this.node.glassOverlay.setAttribute("filled","true");this.node.glassOverlay.setAttribute("fillcolor","white");this.node.glassOverlay.setAttribute("stroked","false");var a=document.createElement("v:fill");a.setAttribute("type","gradient");
-a.setAttribute("color","white");a.setAttribute("color2","white");a.setAttribute("opacity","90%");a.setAttribute("o:opacity2","15%");a.setAttribute("angle","180");this.node.glassOverlay.appendChild(a);this.node.appendChild(this.node.glassOverlay)}var a=this.bounds,b=Math.ceil(this.strokewidth*this.scale/2+1),b="m "+-b+" "+-b+" l "+-b+" "+Math.round(a.height*0.4)+" c "+Math.round(a.width*0.3)+" "+Math.round(a.height*0.6)+" "+Math.round(a.width*0.7)+" "+Math.round(a.height*0.6)+" "+Math.round(a.width+
-b)+" "+Math.round(a.height*0.4)+" l "+Math.round(a.width+b)+" "+-b+" x e";this.node.glassOverlay.style.position="absolute";this.node.glassOverlay.style.width=a.width+"px";this.node.glassOverlay.style.height=a.height+"px";this.node.glassOverlay.setAttribute("coordsize",Math.round(this.bounds.width)+","+Math.round(this.bounds.height));this.node.glassOverlay.setAttribute("path",b)}else if(this.node.glassOverlay!=null){this.node.glassOverlay.parentNode.removeChild(this.node.glassOverlay);this.node.glassOverlay=
-null}};
-mxShape.prototype.updateSvgGlassPane=function(){if(this.node.nodeName=="g"&&this.style!=null&&mxUtils.getValue(this.style,mxConstants.STYLE_GLASS,0)==1){if(this.node.glassOverlay==null){if(this.node.ownerSVGElement.glassGradient==null){var a=document.createElementNS(mxConstants.NS_SVG,"linearGradient");a.setAttribute("x1","0%");a.setAttribute("y1","0%");a.setAttribute("x2","0%");a.setAttribute("y2","100%");var b=document.createElementNS(mxConstants.NS_SVG,"stop");b.setAttribute("offset","0%");b.setAttribute("style",
-"stop-color:#ffffff;stop-opacity:0.9");a.appendChild(b);b=document.createElementNS(mxConstants.NS_SVG,"stop");b.setAttribute("offset","100%");b.setAttribute("style","stop-color:#ffffff;stop-opacity:0.1");a.appendChild(b);for(b=0;document.getElementById("mx-glass-gradient-"+b)!=null;)b++;a.setAttribute("id","mx-glass-gradient-"+b);this.node.ownerSVGElement.appendChild(a);this.node.ownerSVGElement.glassGradient=a}this.node.glassOverlay=document.createElementNS(mxConstants.NS_SVG,"path");this.node.glassOverlay.setAttribute("style",
-"fill:url(#"+this.node.ownerSVGElement.glassGradient.getAttribute("id")+");");this.node.appendChild(this.node.glassOverlay)}a=this.bounds;b=Math.ceil(this.strokewidth*this.scale/2);this.node.glassOverlay.setAttribute("d","m "+(a.x-b)+","+(a.y-b)+" L "+(a.x-b)+","+(a.y+a.height*0.4)+" Q "+(a.x+a.width*0.5)+","+(a.y+a.height*0.7)+" "+(a.x+a.width+b)+","+(a.y+a.height*0.4)+" L "+(a.x+a.width+b)+","+(a.y-b)+" z")}else if(this.node.glassOverlay!=null){this.node.glassOverlay.parentNode.removeChild(this.node.glassOverlay);
-this.node.glassOverlay=null}};mxShape.prototype.redrawVml=function(){this.node.style.visibility="hidden";this.updateVmlShape(this.node);this.updateVmlGlassPane();this.node.style.visibility="visible"};mxShape.prototype.redrawHtml=function(){this.updateHtmlShape(this.node)};mxShape.prototype.getRotation=function(){var a=this.rotation||0;this.direction!=null&&(this.direction=="north"?a=a+270:this.direction=="west"?a=a+180:this.direction=="south"&&(a=a+90));return a};
-mxShape.prototype.createPath=function(a){var b=this.bounds.x,c=this.bounds.y,d=this.bounds.width,e=this.bounds.height,f=0;if(this.direction=="north"||this.direction=="south")var f=(d-e)/2,b=b+f,c=c+(e-d)/2,g=d,d=e,e=g;var h=this.getRotation(),g=null;if(this.dialect==mxConstants.DIALECT_SVG){g=new mxPath("svg");g.setTranslate(b,c);if(h!=0){var f=this.bounds.getCenterX(),k=this.bounds.getCenterY(),h="rotate("+h+" "+f+" "+k+")";this.innerNode!=null&&this.innerNode.setAttribute("transform",h);this.foreground!=
-null&&this.foreground.setAttribute("transform",h);this.shadowNode!=null&&this.shadowNode.setAttribute("transform",this.getSvgShadowTransform()+" "+h)}}else{g=new mxPath("vml");g.setTranslate(f,-f);g.scale=this.vmlScale;if(h!=0)this.node.style.rotation=h}this.redrawPath(g,b,c,d,e,a);return g.getPath()};mxShape.prototype.redrawPath=function(){};function mxStencil(a){this.desc=a;this.parseDescription();this.parseConstraints()}mxStencil.prototype.desc=null;mxStencil.prototype.constraints=null;
-mxStencil.prototype.aspect=null;mxStencil.prototype.w0=null;mxStencil.prototype.h0=null;mxStencil.prototype.bgNode=null;mxStencil.prototype.fgNode=null;mxStencil.prototype.strokewidth=null;
-mxStencil.prototype.parseDescription=function(){this.fgNode=this.desc.getElementsByTagName("foreground")[0];this.bgNode=this.desc.getElementsByTagName("background")[0];this.w0=Number(this.desc.getAttribute("w")||100);this.h0=Number(this.desc.getAttribute("h")||100);var a=this.desc.getAttribute("aspect");this.aspect=a!=null?a:"variable";a=this.desc.getAttribute("strokewidth");this.strokewidth=a!=null?a:"1"};
-mxStencil.prototype.parseConstraints=function(){var a=this.desc.getElementsByTagName("connections")[0];if(a!=null){a=mxUtils.getChildNodes(a);if(a!=null&&a.length>0){this.constraints=[];for(var b=0;b<a.length;b++)this.constraints.push(this.parseConstraint(a[b]))}}};mxStencil.prototype.parseConstraint=function(a){var b=Number(a.getAttribute("x")),c=Number(a.getAttribute("y")),a=a.getAttribute("perimeter")=="1";return new mxConnectionConstraint(new mxPoint(b,c),a)};
-mxStencil.prototype.evaluateAttribute=function(a,b,c){b=a.getAttribute(b);if(b==null){a=mxUtils.getTextContent(a);if(a!=null){a=mxUtils.eval(a);typeof a=="function"&&(b=a(c))}}return b};
-mxStencil.prototype.renderDom=function(a,b,c,d){var e=a.dialect!=mxConstants.DIALECT_SVG,f=document.documentMode==8?1:a.vmlScale,g=a.rotation||0,h=false,k=a.style[mxConstants.STYLE_STENCIL_FLIPH],i=a.style[mxConstants.STYLE_STENCIL_FLIPV];if(k?!i:i)g=g*-1;if(a.direction!=null){a.direction=="north"?g=g+270:a.direction=="west"?g=g+180:a.direction=="south"&&(g=g+90);h=a.direction=="north"||a.direction=="south"}if(k&&i){g=g+180;i=k=false}var l="";if(e){if(k)c.style.flip="x";else if(i)c.style.flip="y";
-if(g!=0)c.style.rotation=g}else{if(k||i){var m=1,n=1,o=0,p=0;if(k){m=-1;o=-b.width-2*b.x}if(i){n=-1;p=-b.height-2*b.y}l="scale("+m+" "+n+") translate("+o+" "+p+")"}if(g!=0){k=b.getCenterX();i=b.getCenterY();l=l+(" rotate("+g+" "+k+" "+i+")")}}var q=d==null;if(this.bgNode!=null||this.fgNode!=null){var t=e&&d==null?0:b.x,u=e&&d==null?0:b.y,m=b.width/this.w0,n=b.height/this.h0;this.lastMoveY=this.lastMoveX=0;if(h){n=b.width/this.h0;m=b.height/this.w0;g=(b.width-b.height)/2;t=t+g;u=u-g}if(this.aspect==
-"fixed"){m=n=Math.min(m,n);if(h){t=t+(b.height-this.w0*m)/2;u=u+(b.width-this.h0*n)/2}else{t=t+(b.width-this.w0*m)/2;u=u+(b.height-this.h0*n)/2}}if(e){m=m*f;n=n*f;t=t*f;u=u*f}var v=Math.min(m,n),w=[],r=d!=null?d:{fillColorAssigned:false,fill:a.fill,stroke:a.stroke,strokeWidth:this.strokewidth=="inherit"?Number(a.strokewidth)*a.scale:Number(this.strokewidth)*v/(e?f:1),dashed:a.isDashed,dashpattern:[3,3],alpha:a.opacity,linejoin:"miter",fontColor:"#000000",fontSize:mxConstants.DEFAULT_FONTSIZE,fontFamily:mxConstants.DEFAULT_FONTFAMILY,
-fontStyle:0},s=null,y=null,B=function(b,c){var d=Math.max(1,c.strokeWidth);if(e){b.strokeweight=Math.round(d)+"px";if(c.fill!=null){var d=!c.fillColorAssigned?a.gradient:null,f=document.createElement("v:fill");a.updateVmlFill(f,c.fill,d,a.gradientDirection,c.alpha);b.appendChild(f)}else b.filled="false";if(c.stroke!=null){b.stroked="true";b.strokecolor=c.stroke}else b.stroked="false";b.style.position="absolute"}else{b.setAttribute("stroke-width",d);c.fill!=null&&c.fillColorAssigned&&b.setAttribute("fill",
-c.fill);c.stroke!=null&&b.setAttribute("stroke",c.stroke)}},z=function(a){s!=null&&y!=null&&y.push(a)},x=function(a){return e?Math.round(a):a},C=function(d){var g=d.nodeName,h=g=="fill",i=g=="stroke",k=g=="fillstroke";if(g=="save"){w.push(r);r=mxUtils.clone(r)}else if(g=="restore")r=w.pop();else if(g=="path"){y=[];if(e){s=document.createElement("v:shape");B.call(this,s,r);k=Math.round(b.width)*f;g=Math.round(b.height)*f;s.style.width=k+"px";s.style.height=g+"px";s.coordsize=k+","+g}else{s=document.createElementNS(mxConstants.NS_SVG,
-"path");B.call(this,s,r);l.length>0&&s.setAttribute("transform",l);d.getAttribute("crisp")=="1"&&s.setAttribute("shape-rendering","crispEdges")}for(d=d.firstChild;d!=null;){d.nodeType==mxConstants.NODETYPE_ELEMENT&&C.call(this,d);d=d.nextSibling}if(e){z("e");s.path=y.join("")}else s.setAttribute("d",y.join(""))}else if(g=="move"){h=e?"m":"M";this.lastMoveX=x(t+Number(d.getAttribute("x"))*m);this.lastMoveY=x(u+Number(d.getAttribute("y"))*n);z(h+" "+this.lastMoveX+" "+this.lastMoveY)}else if(g=="line"){h=
-e?"l":"L";this.lastMoveX=x(t+Number(d.getAttribute("x"))*m);this.lastMoveY=x(u+Number(d.getAttribute("y"))*n);z(h+" "+this.lastMoveX+" "+this.lastMoveY)}else if(g=="quad")if(e){var h=this.lastMoveX,o=this.lastMoveY,i=t+Number(d.getAttribute("x1"))*m,g=u+Number(d.getAttribute("y1"))*n,k=t+Number(d.getAttribute("x2"))*m,d=u+Number(d.getAttribute("y2"))*n,o=o+2/3*(g-o),p=k+2/3*(i-k),g=d+2/3*(g-d);z("c "+Math.round(h+2/3*(i-h))+" "+Math.round(o)+" "+Math.round(p)+" "+Math.round(g)+" "+Math.round(k)+" "+
-Math.round(d));this.lastMoveX=k;this.lastMoveY=d}else{this.lastMoveX=t+Number(d.getAttribute("x2"))*m;this.lastMoveY=u+Number(d.getAttribute("y2"))*n;z("Q "+(t+Number(d.getAttribute("x1"))*m)+" "+(u+Number(d.getAttribute("y1"))*n)+" "+this.lastMoveX+" "+this.lastMoveY)}else if(g=="curve"){h=e?"c":"C";this.lastMoveX=x(t+Number(d.getAttribute("x3"))*m);this.lastMoveY=x(u+Number(d.getAttribute("y3"))*n);z(h+" "+x(t+Number(d.getAttribute("x1"))*m)+" "+x(u+Number(d.getAttribute("y1"))*n)+" "+x(t+Number(d.getAttribute("x2"))*
-m)+" "+x(u+Number(d.getAttribute("y2"))*n)+" "+this.lastMoveX+" "+this.lastMoveY)}else if(g=="close")z(e?"x":"Z");else if(g=="rect"||g=="roundrect"){o=g=="roundrect";i=x(t+Number(d.getAttribute("x"))*m);h=x(u+Number(d.getAttribute("y"))*n);k=x(Number(d.getAttribute("w"))*m);g=x(Number(d.getAttribute("h"))*n);p=d.getAttribute("arcsize");p==0&&(p=mxConstants.RECTANGLE_ROUNDING_FACTOR*100);if(e){s=document.createElement(o?"v:roundrect":"v:rect");s.style.left=i+"px";s.style.top=h+"px";s.style.width=k+
-"px";s.style.height=g+"px";o&&s.setAttribute("arcsize",""+p+"%")}else{s=document.createElementNS(mxConstants.NS_SVG,"rect");s.setAttribute("x",i);s.setAttribute("y",h);s.setAttribute("width",k);s.setAttribute("height",g);if(o){h=Number(p)/100;h=Math.min(k*h,g*h);s.setAttribute("rx",h);s.setAttribute("ry",h)}l.length>0&&s.setAttribute("transform",l);d.getAttribute("crisp")=="1"&&s.setAttribute("shape-rendering","crispEdges")}B.call(this,s,r)}else if(g=="ellipse"){i=x(t+Number(d.getAttribute("x"))*
-m);h=x(u+Number(d.getAttribute("y"))*n);k=x(Number(d.getAttribute("w"))*m);g=x(Number(d.getAttribute("h"))*n);if(e){s=document.createElement("v:arc");s.startangle="0";s.endangle="360";s.style.left=i+"px";s.style.top=h+"px";s.style.width=k+"px";s.style.height=g+"px"}else{s=document.createElementNS(mxConstants.NS_SVG,"ellipse");s.setAttribute("cx",i+k/2);s.setAttribute("cy",h+g/2);s.setAttribute("rx",k/2);s.setAttribute("ry",g/2);l.length>0&&s.setAttribute("transform",l)}B.call(this,s,r)}else if(g==
-"arc"){var k=Number(d.getAttribute("rx"))*m,g=Number(d.getAttribute("ry"))*n,o=Number(d.getAttribute("x-axis-rotation")),p=Number(d.getAttribute("large-arc-flag")),A=Number(d.getAttribute("sweep-flag")),i=t+Number(d.getAttribute("x"))*m,h=u+Number(d.getAttribute("y"))*n;if(e){h=mxUtils.arcToCurves(this.lastMoveX,this.lastMoveY,k,g,o,p,A,i,h);for(d=0;d<h.length;d=d+6){z("c "+Math.round(h[d])+" "+Math.round(h[d+1])+" "+Math.round(h[d+2])+" "+Math.round(h[d+3])+" "+Math.round(h[d+4])+" "+Math.round(h[d+
-5]));this.lastMoveX=h[d+4];this.lastMoveY=h[d+5]}}else{z("A "+k+","+g+" "+o+" "+p+","+A+" "+i+","+h);this.lastMoveX=t+i;this.lastMoveY=u+h}}else if(g=="image"){o=this.evaluateAttribute(d,"src",a.state);if(o!=null){i=x(t+Number(d.getAttribute("x"))*m);h=x(u+Number(d.getAttribute("y"))*n);k=x(Number(d.getAttribute("w"))*m);g=x(Number(d.getAttribute("h"))*n);p=d.getAttribute("flipH")=="1";A=d.getAttribute("flipV")=="1";if(e){s=document.createElement("v:image");s.style.filter="alpha(opacity="+r.alpha+
-")";s.style.left=i+"px";s.style.top=h+"px";s.style.width=k+"px";s.style.height=g+"px";s.src=o;if(p&&A)s.style.rotation="180";else if(p)s.style.flip="x";else if(A)s.style.flip="y"}else{s=document.createElementNS(mxConstants.NS_SVG,"image");s.setAttributeNS(mxConstants.NS_XLINK,"xlink:href",o);s.setAttribute("opacity",r.alpha/100);s.setAttribute("x",i);s.setAttribute("y",h);s.setAttribute("width",k);s.setAttribute("height",g);s.setAttribute("preserveAspectRatio","none");if(p||A){var D=1,E=1,o=d=0;if(p){D=
--1;d=-k-2*i}if(A){E=-1;o=-g-2*h}s.setAttribute("transform",l+"scale("+D+" "+E+") translate("+d+" "+o+") ")}else s.setAttribute("transform",l)}c.appendChild(s)}}else if(g=="include-shape"){o=mxStencilRegistry.getStencil(d.getAttribute("name"));if(o!=null){i=t+Number(d.getAttribute("x"))*m;h=u+Number(d.getAttribute("y"))*n;k=Number(d.getAttribute("w"))*m;g=Number(d.getAttribute("h"))*n;o.renderDom(a,new mxRectangle(i,h,k,g),c,r)}}else if(g=="text"){k=this.evaluateAttribute(d,"str",a.state);if(k!=null){i=
-x(t+Number(d.getAttribute("x"))*m);h=x(u+Number(d.getAttribute("y"))*n);g=d.getAttribute("align")||"left";o=d.getAttribute("valign")||"top";if(e){s=document.createElement("v:shape");s.style.position="absolute";s.style.width="1px";s.style.height="1px";s.style.left=i+"px";s.style.top=h+"px";d=document.createElement("v:fill");d.color=r.fontColor;d.on="true";s.appendChild(d);d=document.createElement("v:stroke");d.on="false";s.appendChild(d);d=document.createElement("v:path");d.textpathok="true";d.v="m "+
-i+" "+h+" l "+(i+1)+" "+h;s.appendChild(d);d=document.createElement("v:textpath");d.style.cssText="v-text-align:"+g;d.style.fontSize=Math.round(r.fontSize/f)+"px";d.style.fontFamily=r.fontFamily;d.string=k;d.on="true";if((r.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD)d.style.fontWeight="bold";if((r.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC)d.style.fontStyle="italic";if((r.fontStyle&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE)d.style.textDecoration="underline";
-if(o=="top")s.style.top=h+r.fontSize/2+"px";else if(o=="bottom")s.style.top=h-r.fontSize/3+"px";s.appendChild(d)}else{s=document.createElementNS(mxConstants.NS_SVG,"text");s.setAttribute("fill",r.fontColor);s.setAttribute("font-family",r.fontFamily);s.setAttribute("font-size",r.fontSize);s.setAttribute("stroke","none");s.setAttribute("x",i);s.appendChild(document.createTextNode(k));(r.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&s.setAttribute("font-weight","bold");(r.fontStyle&mxConstants.FONT_ITALIC)==
-mxConstants.FONT_ITALIC&&s.setAttribute("font-style","italic");(r.fontStyle&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&s.setAttribute("text-decoration",uline);g=="left"?g="start":g=="center"?g="middle":g=="right"&&(g="end");s.setAttribute("text-anchor",g);if(o=="top"){s.setAttribute("y",h+r.fontSize/5);s.setAttribute("dy","1ex")}else if(o=="middle"){s.setAttribute("y",h+r.fontSize/8);s.setAttribute("dy","0.5ex")}else s.setAttribute("y",h);l.length>0&&s.setAttribute("transform",l)}c.appendChild(s)}}else if(h||
-i||k){if(s!=null){g=null;if(r.dashed){g=e?v:Number(s.getAttribute("stroke-width"));o=[];for(d=0;d<r.dashpattern.length;d++)o.push(Math.max(1,Math.round(Number(r.dashpattern[d])*g)));g=o.join(" ")}if(i||k)if(e){d=document.createElement("v:stroke");d.endcap=r.linecap||"flat";d.joinstyle=r.linejoin||"miter";d.miterlimit=r.miterlimit||"10";s.appendChild(d);if(g!=null)d.dashstyle=g}else{r.linejoin!=null&&s.setAttribute("stroke-linejoin",r.linejoin);if(r.linecap!=null){d=r.linecap;d=="flat"&&(d="butt");
-s.setAttribute("stroke-linecap",d)}r.miterlimit!=null&&s.setAttribute("stroke-miterlimit",r.miterlimit);g!=null&&s.setAttribute("stroke-dasharray",g)}if(q&&a.isShadow){d=mxConstants.SHADOW_OFFSET_X*a.scale;o=mxConstants.SHADOW_OFFSET_Y*a.scale;if(e){p=document.createElement("v:shadow");p.setAttribute("on","true");p.setAttribute("color",mxConstants.SHADOWCOLOR);p.setAttribute("offset",Math.round(d)+"px,"+Math.round(o)+"px");p.setAttribute("opacity",mxConstants.SHADOW_OPACITY*100+"%");d=document.createElement("v:stroke");
-d.endcap=r.linecap||"flat";d.joinstyle=r.linejoin||"miter";d.miterlimit=r.miterlimit||"10";if(g!=null)d.dashstyle=g;p.appendChild(d);s.appendChild(p)}else{p=s.cloneNode(true);p.setAttribute("stroke",mxConstants.SHADOWCOLOR);r.fill!=null&&(h||k)?p.setAttribute("fill",mxConstants.SHADOWCOLOR):p.setAttribute("fill","none");p.setAttribute("transform","translate("+d+" "+o+") "+(p.getAttribute("transform")||""));p.setAttribute("opacity",mxConstants.SHADOW_OPACITY);c.appendChild(p)}}if(h)e?s.stroked="false":
-s.setAttribute("stroke","none");else if(i)e?s.filled="false":s.setAttribute("fill","none");c.appendChild(s)}q&&(q=false)}else if(g=="linecap")r.linecap=d.getAttribute("cap");else if(g=="linejoin")r.linejoin=d.getAttribute("join");else if(g=="miterlimit")r.miterlimit=d.getAttribute("limit");else if(g=="dashed")r.dashed=d.getAttribute("dashed")=="1";else if(g=="dashpattern"){d=d.getAttribute("pattern");if(d!=null&&d.length>0)r.dashpattern=d.split(" ")}else if(g=="strokewidth"){r.strokeWidth=d.getAttribute("width")*
-v;if(e)r.strokeWidth=r.strokeWidth/f}else if(g=="strokecolor")r.stroke=d.getAttribute("color");else if(g=="fillcolor"){r.fill=d.getAttribute("color");r.fillColorAssigned=true}else if(g=="alpha")r.alpha=Number(d.getAttribute("alpha"));else if(g=="fontcolor")r.fontColor=d.getAttribute("color");else if(g=="fontsize")r.fontSize=Number(d.getAttribute("size"))*v;else if(g=="fontfamily")r.fontFamily=d.getAttribute("family");else if(g=="fontstyle")r.fontStyle=Number(d.getAttribute("style"))};if(!e){d=document.createElementNS(mxConstants.NS_SVG,
-"rect");d.setAttribute("x",b.x);d.setAttribute("y",b.y);d.setAttribute("width",b.width);d.setAttribute("height",b.height);d.setAttribute("fill","none");d.setAttribute("stroke","none");c.appendChild(d)}if(this.bgNode!=null)for(d=this.bgNode.firstChild;d!=null;){d.nodeType==mxConstants.NODETYPE_ELEMENT&&C.call(this,d);d=d.nextSibling}else q=false;if(this.fgNode!=null)for(d=this.fgNode.firstChild;d!=null;){d.nodeType==mxConstants.NODETYPE_ELEMENT&&C.call(this,d);d=d.nextSibling}}};
-mxStencil.prototype.drawShape=function(a,b,c,d){d=d?this.bgNode:this.fgNode;if(d!=null){var e=mxUtils.getValue(b.style,mxConstants.STYLE_DIRECTION,null),c=this.computeAspect(b,c,e),e=Math.min(c.width,c.height),e=this.strokewidth=="inherit"?Number(mxUtils.getNumber(b.style,mxConstants.STYLE_STROKEWIDTH,1))*b.view.scale:Number(this.strokewidth)*e;this.lastMoveY=this.lastMoveX=0;a.setStrokeWidth(e);for(d=d.firstChild;d!=null;){d.nodeType==mxConstants.NODETYPE_ELEMENT&&this.drawNode(a,b,d,c);d=d.nextSibling}return true}return false};
-mxStencil.prototype.computeAspect=function(a,b,c){var a=b.x,d=b.y,e=b.width/this.w0,f=b.height/this.h0;if(c=c=="north"||c=="south")var f=b.width/this.h0,e=b.height/this.w0,g=(b.width-b.height)/2,a=a+g,d=d-g;if(this.aspect=="fixed"){e=f=Math.min(e,f);if(c){a=a+(b.height-this.w0*e)/2;d=d+(b.width-this.h0*f)/2}else{a=a+(b.width-this.w0*e)/2;d=d+(b.height-this.h0*f)/2}}return new mxRectangle(a,d,e,f)};
-mxStencil.prototype.drawNode=function(a,b,c,d){var e=c.nodeName,f=d.x,g=d.y,h=d.width,k=d.height,i=Math.min(h,k);if(e=="save")a.save();else if(e=="restore")a.restore();else if(e=="path"){a.begin();for(c=c.firstChild;c!=null;){c.nodeType==mxConstants.NODETYPE_ELEMENT&&this.drawNode(a,b,c,d);c=c.nextSibling}}else if(e=="close")a.close();else if(e=="move"){this.lastMoveX=f+Number(c.getAttribute("x"))*h;this.lastMoveY=g+Number(c.getAttribute("y"))*k;a.moveTo(this.lastMoveX,this.lastMoveY)}else if(e==
-"line"){this.lastMoveX=f+Number(c.getAttribute("x"))*h;this.lastMoveY=g+Number(c.getAttribute("y"))*k;a.lineTo(this.lastMoveX,this.lastMoveY)}else if(e=="quad"){this.lastMoveX=f+Number(c.getAttribute("x2"))*h;this.lastMoveY=g+Number(c.getAttribute("y2"))*k;a.quadTo(f+Number(c.getAttribute("x1"))*h,g+Number(c.getAttribute("y1"))*k,this.lastMoveX,this.lastMoveY)}else if(e=="curve"){this.lastMoveX=f+Number(c.getAttribute("x3"))*h;this.lastMoveY=g+Number(c.getAttribute("y3"))*k;a.curveTo(f+Number(c.getAttribute("x1"))*
-h,g+Number(c.getAttribute("y1"))*k,f+Number(c.getAttribute("x2"))*h,g+Number(c.getAttribute("y2"))*k,this.lastMoveX,this.lastMoveY)}else if(e=="arc")for(var b=Number(c.getAttribute("rx"))*h,i=Number(c.getAttribute("ry"))*k,d=Number(c.getAttribute("x-axis-rotation")),e=Number(c.getAttribute("large-arc-flag")),l=Number(c.getAttribute("sweep-flag")),f=f+Number(c.getAttribute("x"))*h,g=g+Number(c.getAttribute("y"))*k,c=mxUtils.arcToCurves(this.lastMoveX,this.lastMoveY,b,i,d,e,l,f,g),h=0;h<c.length;h=
-h+6){a.curveTo(c[h],c[h+1],c[h+2],c[h+3],c[h+4],c[h+5]);this.lastMoveX=c[h+4];this.lastMoveY=c[h+5]}else if(e=="rect")a.rect(f+Number(c.getAttribute("x"))*h,g+Number(c.getAttribute("y"))*k,Number(c.getAttribute("w"))*h,Number(c.getAttribute("h"))*k);else if(e=="roundrect"){b=c.getAttribute("arcsize");b==0&&(b=mxConstants.RECTANGLE_ROUNDING_FACTOR*100);i=Number(c.getAttribute("w"))*h;d=Number(c.getAttribute("h"))*k;b=Number(b)/100;b=Math.min(i*b,d*b);a.roundrect(f+Number(c.getAttribute("x"))*h,g+Number(c.getAttribute("y"))*
-k,i,d,b,b)}else if(e=="ellipse")a.ellipse(f+Number(c.getAttribute("x"))*h,g+Number(c.getAttribute("y"))*k,Number(c.getAttribute("w"))*h,Number(c.getAttribute("h"))*k);else if(e=="image"){b=this.evaluateAttribute(c,"src",b);a.image(f+Number(c.getAttribute("x"))*h,g+Number(c.getAttribute("y"))*k,Number(c.getAttribute("w"))*h,Number(c.getAttribute("h"))*k,b,false,c.getAttribute("flipH")=="1",c.getAttribute("flipV")=="1")}else if(e=="text"){b=this.evaluateAttribute(c,"str",b);a.text(f+Number(c.getAttribute("x"))*
-h,g+Number(c.getAttribute("y"))*k,0,0,b,c.getAttribute("align"),c.getAttribute("valign"),c.getAttribute("vertical"))}else if(e=="include-shape"){e=mxStencilRegistry.getStencil(c.getAttribute("name"));if(e!=null){f=f+Number(c.getAttribute("x"))*h;g=g+Number(c.getAttribute("y"))*k;i=Number(c.getAttribute("w"))*h;d=Number(c.getAttribute("h"))*k;c=new mxRectangle(f,g,i,d);e.drawShape(a,b,c,true);e.drawShape(a,b,c,false)}}else if(e=="fillstroke")a.fillAndStroke();else if(e=="fill")a.fill();else if(e==
-"stroke")a.stroke();else if(e=="strokewidth")a.setStrokeWidth(Number(c.getAttribute("width"))*i);else if(e=="dashed")a.setDashed(c.getAttribute("dashed")=="1");else if(e=="dashpattern"){c=c.getAttribute("pattern");if(c!=null){c=c.split(" ");k=[];for(h=0;h<c.length;h++)c[h].length>0&&k.push(Number(c[h])*i);c=k.join(" ");a.setDashPattern(c)}}else e=="strokecolor"?a.setStrokeColor(c.getAttribute("color")):e=="linecap"?a.setLineCap(c.getAttribute("cap")):e=="linejoin"?a.setLineJoin(c.getAttribute("join")):
-e=="miterlimit"?a.setMiterLimit(Number(c.getAttribute("limit"))):e=="fillcolor"?a.setFillColor(c.getAttribute("color")):e=="fontcolor"?a.setFontColor(c.getAttribute("color")):e=="fontstyle"?a.setFontStyle(c.getAttribute("style")):e=="fontfamily"?a.setFontFamily(c.getAttribute("family")):e=="fontsize"&&a.setFontSize(Number(c.getAttribute("size"))*i)};var mxStencilRegistry={stencils:[],addStencil:function(a,b){mxStencilRegistry.stencils[a]=b},getStencil:function(a){return mxStencilRegistry.stencils[a]}};
-function mxStencilShape(a){this.stencil=a}mxStencilShape.prototype=new mxShape;mxStencilShape.prototype.constructor=mxStencilShape;mxStencilShape.prototype.mixedModeHtml=!1;mxStencilShape.prototype.preferModeHtml=!1;mxStencilShape.prototype.stencil=null;mxStencilShape.prototype.state=null;mxStencilShape.prototype.vmlScale=4;mxStencilShape.prototype.apply=function(a){this.state=a;mxShape.prototype.apply.apply(this,arguments)};
-mxStencilShape.prototype.createSvg=function(){var a=document.createElementNS(mxConstants.NS_SVG,"g");this.configureSvgShape(a);return a};mxStencilShape.prototype.configureHtmlShape=function(a){mxShape.prototype.configureHtmlShape.apply(this,arguments);if(!mxUtils.isVml(a))a.style.overflow="visible"};mxStencilShape.prototype.createVml=function(){var a=document.createElement(document.documentMode==8?"div":"v:group");this.configureTransparentBackground(a);a.style.position="absolute";return a};
-mxStencilShape.prototype.configureVmlShape=function(){};mxStencilShape.prototype.redraw=function(){this.updateBoundingBox();if(this.dialect==mxConstants.DIALECT_SVG)this.redrawShape();else{this.node.style.visibility="hidden";this.redrawShape();this.node.style.visibility="visible"}};
-mxStencilShape.prototype.redrawShape=function(){if(this.dialect!=mxConstants.DIALECT_SVG){this.node.style.left=Math.round(this.bounds.x)+"px";this.node.style.top=Math.round(this.bounds.y)+"px";var a=Math.round(this.bounds.width),b=Math.round(this.bounds.height);this.node.style.width=a+"px";this.node.style.height=b+"px";var c=this.node;if(this.node.nodeName=="DIV"){c=document.createElement("v:group");c.style.position="absolute";c.style.left="0px";c.style.top="0px";c.style.width=a+"px";c.style.height=
-b+"px"}else c.innerHTML="";if(mxUtils.isVml(c)){var d=document.documentMode!=8?this.vmlScale:1;c.coordsize=a*d+","+b*d}this.stencil.renderDom(this,this.bounds,c);if(this.node!=c)this.node.innerHTML=c.outerHTML}else{for(;this.node.firstChild!=null;)this.node.removeChild(this.node.firstChild);this.stencil.renderDom(this,this.bounds,this.node)}};
-var mxMarker={markers:[],paintMarker:function(a,b,c,d,e,f,g,h,k,i,l,m){var n=mxMarker.markers[b],o=null;if(n!=null){var o=mxUtils.isVml(a),p=d.x-c.x,c=d.y-c.y;if(isNaN(p)||isNaN(c))return;var q=Math.max(1,Math.sqrt(p*p+c*c)),p=p*h/q,c=c*h/q,d=d.clone();if(o){d.x=d.x-k;d.y=d.y-i}k=true;if(m[l?mxConstants.STYLE_STARTFILL:mxConstants.STYLE_ENDFILL]==0)k=false;if(o){a.strokecolor=e;k?a.fillcolor=e:a.filled="false"}else{a.setAttribute("stroke",e);l=m.opacity!=null?m.opacity/100:1;a.setAttribute("stroke-opacity",
-l);if(k){a.setAttribute("fill",e);a.setAttribute("fill-opacity",l)}else a.setAttribute("fill","none")}o=n.call(this,a,b,d,p,c,f,g,h,o)}return o}};
-(function(){var a=function(a,c,d,e,f,g,h,k,i){var l=e*g*1.118,m=f*g*1.118;d.x=d.x-l;d.y=d.y-m;e=e*(h+g);f=f*(h+g);if(i){a.path="m"+Math.round(d.x)+","+Math.round(d.y)+" l"+Math.round(d.x-e-f/2)+" "+Math.round(d.y-f+e/2)+(c!=mxConstants.ARROW_CLASSIC?"":" "+Math.round(d.x-e*3/4)+" "+Math.round(d.y-f*3/4))+" "+Math.round(d.x+f/2-e)+" "+Math.round(d.y-f-e/2)+" x e";a.setAttribute("strokeweight",g*k+"px")}else{a.setAttribute("d","M "+d.x+" "+d.y+" L "+(d.x-e-f/2)+" "+(d.y-f+e/2)+(c!=mxConstants.ARROW_CLASSIC?
-"":" L "+(d.x-e*3/4)+" "+(d.y-f*3/4))+" L "+(d.x+f/2-e)+" "+(d.y-f-e/2)+" z");a.setAttribute("stroke-width",g*k)}a=c!=mxConstants.ARROW_CLASSIC?1:0.75;return new mxPoint(-e*a-l,-f*a-m)};mxMarker.markers[mxConstants.ARROW_CLASSIC]=a;mxMarker.markers[mxConstants.ARROW_BLOCK]=a})();
-mxMarker.markers[mxConstants.ARROW_OPEN]=function(a,b,c,d,e,f,g,h,k){var b=d*f*1.118,i=e*f*1.118;c.x=c.x-b;c.y=c.y-i;d=d*(g+f);e=e*(g+f);if(k){a.path="m"+Math.round(c.x-d-e/2)+" "+Math.round(c.y-e+d/2)+" l"+Math.round(c.x)+" "+Math.round(c.y)+" "+Math.round(c.x+e/2-d)+" "+Math.round(c.y-e-d/2)+" e nf";a.setAttribute("strokeweight",f*h+"px")}else{a.setAttribute("d","M "+(c.x-d-e/2)+" "+(c.y-e+d/2)+" L "+c.x+" "+c.y+" L "+(c.x+e/2-d)+" "+(c.y-e-d/2));a.setAttribute("stroke-width",f*h);a.setAttribute("fill",
-"none")}return new mxPoint(-b*2,-i*2)};
-mxMarker.markers[mxConstants.ARROW_OVAL]=function(a,b,c,d,e,f,g,h,k){d=d*g*(0.5+f/2);e=e*g*(0.5+f/2);b=g*h;g=b/2;if(k){a.path="m"+Math.round(c.x+g)+" "+Math.round(c.y)+" at "+Math.round(c.x-g)+" "+Math.round(c.y-g)+" "+Math.round(c.x+g)+" "+Math.round(c.y+g)+" "+Math.round(c.x+g)+" "+Math.round(c.y)+" "+Math.round(c.x+g)+" "+Math.round(c.y)+" x e";a.setAttribute("strokeweight",f*h+"px")}else{a.setAttribute("d","M "+(c.x-g)+" "+c.y+" a "+g+" "+g+" 0 1,1 "+b+" 0 a "+g+" "+g+" 0 1,1 "+-b+" 0 z");a.setAttribute("stroke-width",
-f*h)}return new mxPoint(-d/(2+f),-e/(2+f))};
-(function(){var a=function(a,c,d,e,f,g,h,k,i){var l=c==mxConstants.ARROW_DIAMOND?0.7071:0.9862,m=e*g*l,l=f*g*l,e=e*(h+g),f=f*(h+g);d.x=d.x-(m+e/2);d.y=d.y-(l+f/2);c=c==mxConstants.ARROW_DIAMOND?2:3.4;if(i){a.path="m"+Math.round(d.x+e/2)+" "+Math.round(d.y+f/2)+" l"+Math.round(d.x-f/c)+" "+Math.round(d.y+e/c)+" "+Math.round(d.x-e/2)+" "+Math.round(d.y-f/2)+" "+Math.round(d.x+f/c)+" "+Math.round(d.y-e/c)+" x e";a.setAttribute("strokeweight",g*k+"px")}else{a.setAttribute("d","M "+(d.x+e/2)+" "+(d.y+
-f/2)+" L "+(d.x-f/c)+" "+(d.y+e/c)+" L "+(d.x-e/2)+" "+(d.y-f/2)+" L "+(d.x+f/c)+" "+(d.y-e/c)+" z");a.setAttribute("stroke-width",g*k)}return new mxPoint(-m-e,-l-f)};mxMarker.markers[mxConstants.ARROW_DIAMOND]=a;mxMarker.markers[mxConstants.ARROW_DIAMOND_THIN]=a})();function mxActor(a,b,c,d){this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=d!=null?d:1}mxActor.prototype=new mxShape;mxActor.prototype.constructor=mxActor;mxActor.prototype.mixedModeHtml=!1;mxActor.prototype.preferModeHtml=!1;
-mxActor.prototype.vmlScale=2;mxActor.prototype.createVml=function(){var a=document.createElement("v:shape");a.style.position="absolute";this.configureVmlShape(a);return a};mxActor.prototype.redrawVml=function(){this.updateVmlShape(this.node);this.node.path=this.createPath()};mxActor.prototype.createSvg=function(){return this.createSvgGroup("path")};
-mxActor.prototype.redrawSvg=function(){var a=Math.round(Math.max(1,this.strokewidth*this.scale));this.innerNode.setAttribute("stroke-width",a);this.innerNode.setAttribute("stroke-linejoin","round");this.crisp&&(this.rotation==null||this.rotation==0)?this.innerNode.setAttribute("shape-rendering","crispEdges"):this.innerNode.removeAttribute("shape-rendering");var b=this.createPath();if(b.length>0){this.innerNode.setAttribute("d",b);if(this.shadowNode!=null){this.shadowNode.setAttribute("transform",
-this.getSvgShadowTransform()+(this.innerNode.getAttribute("transform")||""));this.shadowNode.setAttribute("stroke-width",a);this.shadowNode.setAttribute("d",b)}}else{this.innerNode.removeAttribute("d");this.shadowNode!=null&&this.shadowNode.removeAttribute("d")}if(this.isDashed){a=Math.max(1,Math.round(3*this.scale*this.strokewidth));this.innerNode.setAttribute("stroke-dasharray",a+" "+a)}};
-mxActor.prototype.redrawPath=function(a,b,c,d,e){b=d/3;a.moveTo(0,e);a.curveTo(0,3*e/5,0,2*e/5,d/2,2*e/5);a.curveTo(d/2-b,2*e/5,d/2-b,0,d/2,0);a.curveTo(d/2+b,0,d/2+b,2*e/5,d/2,2*e/5);a.curveTo(d,2*e/5,d,3*e/5,d,e);a.close()};function mxCloud(a,b,c,d){this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=d!=null?d:1}mxCloud.prototype=new mxActor;mxCloud.prototype.constructor=mxActor;
-mxCloud.prototype.redrawPath=function(a,b,c,d,e){a.moveTo(0.25*d,0.25*e);a.curveTo(0.05*d,0.25*e,0,0.5*e,0.16*d,0.55*e);a.curveTo(0,0.66*e,0.18*d,0.9*e,0.31*d,0.8*e);a.curveTo(0.4*d,e,0.7*d,e,0.8*d,0.8*e);a.curveTo(d,0.8*e,d,0.6*e,0.875*d,0.5*e);a.curveTo(d,0.3*e,0.8*d,0.1*e,0.625*d,0.2*e);a.curveTo(0.5*d,0.05*e,0.3*d,0.05*e,0.25*d,0.25*e);a.close()};function mxRectangleShape(a,b,c,d){this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=d!=null?d:1}mxRectangleShape.prototype=new mxShape;
-mxRectangleShape.prototype.constructor=mxRectangleShape;mxRectangleShape.prototype.createVml=function(){var a=document.createElement(this.isRounded?"v:roundrect":"v:rect");this.configureVmlShape(a);return a};mxRectangleShape.prototype.createSvg=function(){return this.createSvgGroup("rect")};function mxEllipse(a,b,c,d){this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=d!=null?d:1}mxEllipse.prototype=new mxShape;mxEllipse.prototype.constructor=mxEllipse;mxEllipse.prototype.mixedModeHtml=!1;
-mxEllipse.prototype.preferModeHtml=!1;mxEllipse.prototype.createVml=function(){var a=document.createElement("v:arc");a.startangle="0";a.endangle="360";this.configureVmlShape(a);return a};mxEllipse.prototype.createSvg=function(){return this.createSvgGroup("ellipse")};mxEllipse.prototype.redrawSvg=function(){this.crisp?this.innerNode.setAttribute("shape-rendering","crispEdges"):this.innerNode.removeAttribute("shape-rendering");this.updateSvgNode(this.innerNode);this.updateSvgNode(this.shadowNode)};
-mxEllipse.prototype.updateSvgNode=function(a){if(a!=null){var b=Math.round(Math.max(1,this.strokewidth*this.scale));a.setAttribute("stroke-width",b);a.setAttribute("cx",this.bounds.x+this.bounds.width/2);a.setAttribute("cy",this.bounds.y+this.bounds.height/2);a.setAttribute("rx",this.bounds.width/2);a.setAttribute("ry",this.bounds.height/2);this.shadowNode!=null&&this.shadowNode.setAttribute("transform",this.getSvgShadowTransform());if(this.isDashed){b=Math.max(1,Math.round(3*this.scale*this.strokewidth));
-a.setAttribute("stroke-dasharray",b+" "+b)}}};function mxDoubleEllipse(a,b,c,d){this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=d!=null?d:1}mxDoubleEllipse.prototype=new mxShape;mxDoubleEllipse.prototype.constructor=mxDoubleEllipse;mxDoubleEllipse.prototype.vmlNodes=mxDoubleEllipse.prototype.vmlNodes.concat(["background","foreground"]);mxDoubleEllipse.prototype.mixedModeHtml=!1;mxDoubleEllipse.prototype.preferModeHtml=!1;mxDoubleEllipse.prototype.vmlScale=2;
-mxDoubleEllipse.prototype.createVml=function(){var a=document.createElement("v:group");this.background=document.createElement("v:arc");this.background.startangle="0";this.background.endangle="360";this.configureVmlShape(this.background);a.appendChild(this.background);this.label=this.background;this.isShadow=false;this.fill=null;this.foreground=document.createElement("v:oval");this.configureVmlShape(this.foreground);a.appendChild(this.foreground);this.stroke=null;this.configureVmlShape(a);return a};
-mxDoubleEllipse.prototype.redrawVml=function(){this.updateVmlShape(this.node);this.updateVmlShape(this.background);this.updateVmlShape(this.foreground);var a=Math.round((this.strokewidth+3)*this.scale)*this.vmlScale,b=Math.round(this.bounds.width*this.vmlScale),c=Math.round(this.bounds.height*this.vmlScale);this.foreground.style.top=a+"px";this.foreground.style.left=a+"px";this.foreground.style.width=Math.max(0,b-2*a)+"px";this.foreground.style.height=Math.max(0,c-2*a)+"px"};
-mxDoubleEllipse.prototype.createSvg=function(){var a=this.createSvgGroup("ellipse");this.foreground=document.createElementNS(mxConstants.NS_SVG,"ellipse");this.stroke!=null?this.foreground.setAttribute("stroke",this.stroke):this.foreground.setAttribute("stroke","none");this.foreground.setAttribute("fill","none");a.appendChild(this.foreground);return a};
-mxDoubleEllipse.prototype.redrawSvg=function(){if(this.crisp){this.innerNode.setAttribute("shape-rendering","crispEdges");this.foreground.setAttribute("shape-rendering","crispEdges")}else{this.innerNode.removeAttribute("shape-rendering");this.foreground.removeAttribute("shape-rendering")}this.updateSvgNode(this.innerNode);this.updateSvgNode(this.shadowNode);this.updateSvgNode(this.foreground,(this.strokewidth+3)*this.scale);if(this.isDashed){var a=Math.max(1,Math.round(3*this.scale*this.strokewidth));
-this.innerNode.setAttribute("stroke-dasharray",a+" "+a)}};
-mxDoubleEllipse.prototype.updateSvgNode=function(a,b){b=b!=null?b:0;if(a!=null){var c=Math.round(Math.max(1,this.strokewidth*this.scale));a.setAttribute("stroke-width",c);a.setAttribute("cx",this.bounds.x+this.bounds.width/2);a.setAttribute("cy",this.bounds.y+this.bounds.height/2);a.setAttribute("rx",Math.max(0,this.bounds.width/2-b));a.setAttribute("ry",Math.max(0,this.bounds.height/2-b));this.shadowNode!=null&&this.shadowNode.setAttribute("transform",this.getSvgShadowTransform())}};
-function mxRhombus(a,b,c,d){this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=d!=null?d:1}mxRhombus.prototype=new mxShape;mxRhombus.prototype.constructor=mxRhombus;mxRhombus.prototype.mixedModeHtml=!1;mxRhombus.prototype.preferModeHtml=!1;mxRhombus.prototype.createHtml=function(){var a=document.createElement("DIV");this.configureHtmlShape(a);return a};mxRhombus.prototype.createVml=function(){var a=document.createElement("v:shape");this.configureVmlShape(a);return a};
-mxRhombus.prototype.createSvg=function(){return this.createSvgGroup("path")};mxRhombus.prototype.redrawVml=function(){this.updateVmlShape(this.node);var a=Math.round(this.bounds.width),b=Math.round(this.bounds.height);this.node.path="m "+Math.round(0+a/2)+" 0 l "+(0+a)+" "+Math.round(0+b/2)+" l "+Math.round(0+a/2)+" "+(0+b)+" l 0 "+Math.round(0+b/2)+" x e"};mxRhombus.prototype.redrawHtml=function(){this.updateHtmlShape(this.node)};
-mxRhombus.prototype.redrawSvg=function(){this.updateSvgNode(this.innerNode);this.shadowNode!=null&&this.updateSvgNode(this.shadowNode)};
-mxRhombus.prototype.updateSvgNode=function(a){var b=Math.round(Math.max(1,this.strokewidth*this.scale));a.setAttribute("stroke-width",b);var b=this.bounds.x,c=this.bounds.y,d=this.bounds.width,e=this.bounds.height,b="M "+Math.round(b+d/2)+" "+Math.round(c)+" L "+Math.round(b+d)+" "+Math.round(c+e/2)+" L "+Math.round(b+d/2)+" "+Math.round(c+e)+" L "+Math.round(b)+" "+Math.round(c+e/2)+" Z ";a.setAttribute("d",b);this.updateSvgTransform(a,a==this.shadowNode);if(this.isDashed){b=Math.max(1,Math.round(3*
-this.scale*this.strokewidth));a.setAttribute("stroke-dasharray",b+" "+b)}};function mxPolyline(a,b,c){this.points=a;this.stroke=b;this.strokewidth=c!=null?c:1}mxPolyline.prototype=new mxShape;mxPolyline.prototype.constructor=mxPolyline;mxPolyline.prototype.addPipe=!0;
-mxPolyline.prototype.create=function(){var a=null;if(this.dialect==mxConstants.DIALECT_SVG)a=this.createSvg();else if(this.dialect==mxConstants.DIALECT_STRICTHTML||this.dialect==mxConstants.DIALECT_PREFERHTML&&this.points!=null&&this.points.length>0){a=document.createElement("DIV");this.configureHtmlShape(a);a.style.borderStyle="";a.style.background=""}else{a=document.createElement("v:shape");this.configureVmlShape(a);var b=document.createElement("v:stroke");if(this.opacity!=null)b.opacity=this.opacity+
-"%";a.appendChild(b)}return a};mxPolyline.prototype.redrawVml=function(){if(this.points!=null&&this.points.length>0&&this.points[0]!=null){this.bounds=new mxRectangle(this.points[0].x,this.points[0].y,0,0);for(var a=1;a<this.points.length;a++)this.bounds.add(new mxRectangle(this.points[a].x,this.points[a].y,0,0))}mxShape.prototype.redrawVml.apply(this,arguments)};mxPolyline.prototype.createSvg=function(){var a=this.createSvgGroup("path");if(this.addPipe){this.pipe=this.createSvgPipe();a.appendChild(this.pipe)}return a};
-mxPolyline.prototype.redrawSvg=function(){this.updateSvgShape(this.innerNode);var a=this.innerNode.getAttribute("d");if(a!=null&&this.pipe!=null){this.pipe.setAttribute("d",a);this.pipe.setAttribute("stroke-width",Math.round(Math.max(1,this.strokewidth*this.scale))+mxShape.prototype.SVG_STROKE_TOLERANCE)}};
-function mxArrow(a,b,c,d,e,f,g){this.points=a;this.fill=b;this.stroke=c;this.strokewidth=d!=null?d:1;this.arrowWidth=e!=null?e:mxConstants.ARROW_WIDTH;this.spacing=f!=null?f:mxConstants.ARROW_SPACING;this.endSize=g!=null?g:mxConstants.ARROW_SIZE}mxArrow.prototype=new mxActor;mxArrow.prototype.constructor=mxArrow;mxArrow.prototype.addPipe=!1;mxArrow.prototype.enableFill=!0;mxArrow.prototype.configureTransparentBackground=function(){};
-mxArrow.prototype.augmentBoundingBox=function(a){a.grow(Math.max(this.arrowWidth/2,this.endSize/2)*this.scale);mxShape.prototype.augmentBoundingBox.apply(this,arguments)};mxArrow.prototype.createVml=function(){if(!this.enableFill)this.fill=null;return mxActor.prototype.createVml.apply(this,arguments)};mxArrow.prototype.createSvg=function(){if(!this.enableFill)this.fill=null;var a=mxActor.prototype.createSvg.apply(this,arguments);if(this.addPipe){this.pipe=this.createSvgPipe();a.appendChild(this.pipe)}return a};
-mxArrow.prototype.reconfigure=function(){if(!this.enableFill)this.fill=null;mxActor.prototype.reconfigure.apply(this,arguments)};mxArrow.prototype.redrawSvg=function(){mxActor.prototype.redrawSvg.apply(this,arguments);if(this.pipe!=null&&this.innerNode.getAttribute("d")!=null){this.pipe.setAttribute("d",this.innerNode.getAttribute("d"));this.pipe.setAttribute("stroke-width",Math.round(this.strokewidth*this.scale)+mxShape.prototype.SVG_STROKE_TOLERANCE)}};
-mxArrow.prototype.redrawPath=function(a,b,c){a.translate.x=a.translate.x-b;a.translate.y=a.translate.y-c;var b=this.spacing*this.scale,d=this.arrowWidth*this.scale,e=this.endSize*this.scale,f=this.points[0],c=this.points[this.points.length-1],g=c.x-f.x,h=c.y-f.y,k=Math.sqrt(g*g+h*h),i=k-2*b-e,g=g/k,h=h/k,k=d*h/3,d=-d*g/3,e=f.x-k/2+b*g,f=f.y-d/2+b*h,l=e+k,m=f+d,n=l+i*g,i=m+i*h,o=n+k,p=i+d,q=o-3*k,t=p-3*d;a.moveTo(e,f);a.lineTo(l,m);a.lineTo(n,i);a.lineTo(o,p);a.lineTo(c.x-b*g,c.y-b*h);a.lineTo(q,t);
-a.lineTo(q+k,t+d);a.lineTo(e,f);a.close()};
-function mxText(a,b,c,d,e,f,g,h,k,i,l,m,n,o,p,q,t,u,v,w){this.value=a;this.bounds=b;this.color=e!=null?e:"black";this.align=c!=null?c:"";this.valign=d!=null?d:"";this.family=f!=null?f:mxConstants.DEFAULT_FONTFAMILY;this.size=g!=null?g:mxConstants.DEFAULT_FONTSIZE;this.fontStyle=h!=null?h:0;this.spacing=parseInt(k||2);this.spacingTop=this.spacing+parseInt(i||0);this.spacingRight=this.spacing+parseInt(l||0);this.spacingBottom=this.spacing+parseInt(m||0);this.spacingLeft=this.spacing+parseInt(n||0);
-this.horizontal=o!=null?o:true;this.background=p;this.border=q;this.wrap=t!=null?t:false;this.clipped=u!=null?u:false;this.overflow=v!=null?v:"visible";this.labelPadding=w!=null?w:0}mxText.prototype=new mxShape;mxText.prototype.constructor=mxText;mxText.prototype.replaceLinefeeds=!0;mxText.prototype.ieVerticalFilter="progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";mxText.prototype.verticalTextDegree=-90;mxText.prototype.forceIgnoreStringSize=!1;
-mxText.prototype.isStyleSet=function(a){return(this.fontStyle&a)==a};mxText.prototype.create=function(a){var b=null;return b=this.dialect==mxConstants.DIALECT_SVG?this.createSvg():this.dialect==mxConstants.DIALECT_STRICTHTML||this.dialect==mxConstants.DIALECT_PREFERHTML||!mxUtils.isVml(a)?mxClient.IS_SVG&&!mxClient.NO_FO?this.createForeignObject():this.createHtml():this.createVml()};mxText.prototype.updateBoundingBox=function(){};
-mxText.prototype.createForeignObject=function(){var a=document.createElementNS(mxConstants.NS_SVG,"g"),b=document.createElementNS(mxConstants.NS_SVG,"foreignObject");b.setAttribute("pointer-events","fill");b.style.overflow=this.overflow=="hidden"?"hidden":"visible";var c=document.createElement("div");c.style.margin="0px";c.style.height="100%";b.appendChild(c);a.appendChild(b);return a};mxText.prototype.createHtml=function(){var a=this.createHtmlTable();a.style.position="absolute";return a};
-mxText.prototype.createVml=function(){return document.createElement("v:textbox")};mxText.prototype.redrawHtml=function(){this.redrawVml()};
-mxText.prototype.getOffset=function(a,b,c,d,e){var f=(e=e!=null?e:this.horizontal)?this.align:this.valign,g=e?this.valign:this.align,h=c-a,k=d-b;f==mxConstants.ALIGN_CENTER||f==mxConstants.ALIGN_MIDDLE?h=Math.round(h/2):f==mxConstants.ALIGN_LEFT||f===mxConstants.ALIGN_TOP?h=e?0:(c-d)/2:e||(h=(c+d)/2-a);g==mxConstants.ALIGN_MIDDLE||g==mxConstants.ALIGN_CENTER?k=Math.round(k/2):g==mxConstants.ALIGN_TOP||g==mxConstants.ALIGN_LEFT?k=e?0:(d+c)/2-b:e||(k=(d-c)/2);return new mxPoint(h,k)};
-mxText.prototype.getSpacing=function(a){var a=a!=null?a:this.horizontal,b=0,c=0,b=this.align==mxConstants.ALIGN_CENTER?(this.spacingLeft-this.spacingRight)/2:this.align==mxConstants.ALIGN_RIGHT?-this.spacingRight:this.spacingLeft,c=this.valign==mxConstants.ALIGN_MIDDLE?(this.spacingTop-this.spacingBottom)/2:this.valign==mxConstants.ALIGN_BOTTOM?-this.spacingBottom:this.spacingTop;return a?new mxPoint(b,c):new mxPoint(c,b)};
-mxText.prototype.createHtmlTable=function(){var a=document.createElement("table");a.style.borderCollapse="collapse";var b=document.createElement("tbody"),c=document.createElement("tr"),d=document.createElement("td");if(document.documentMode>=9)d.style.height="100%";c.appendChild(d);b.appendChild(c);a.appendChild(b);return a};
-mxText.prototype.updateHtmlTable=function(a,b){var b=b!=null?b:1,c=a.firstChild.firstChild.firstChild;if(this.wrap)a.style.width="";if(mxUtils.isNode(this.value)){if(c.firstChild!=this.value){c.firstChild!=null&&c.removeChild(c.firstChild);c.appendChild(this.value)}}else if(this.lastValue!=this.value){c.innerHTML=this.replaceLinefeeds?this.value.replace(/\n/g,"<br/>"):this.value;this.lastValue=this.value}var d=Math.round(this.size*b);a.style.visibility=d<=0?"hidden":"";a.style.fontSize=d+"px";a.style.color=
-this.color;a.style.fontFamily=this.family;a.style.fontWeight=this.isStyleSet(mxConstants.FONT_BOLD)?"bold":"normal";a.style.fontStyle=this.isStyleSet(mxConstants.FONT_ITALIC)?"italic":"";a.style.textDecoration=this.isStyleSet(mxConstants.FONT_UNDERLINE)?"underline":"";if(mxClient.IS_IE)this.isStyleSet(mxConstants.FONT_SHADOW)?c.style.filter="Shadow(Color=#666666,Direction=135,Strength=%)":c.style.removeAttribute("filter");c.style.textAlign=this.align==mxConstants.ALIGN_RIGHT?"right":this.align==mxConstants.ALIGN_CENTER?
-"center":"left";c.style.verticalAlign=this.valign==mxConstants.ALIGN_BOTTOM?"bottom":this.valign==mxConstants.ALIGN_MIDDLE?"middle":"top";c.style.background=this.value.length>0&&this.background!=null?this.background:"";c.style.padding=this.labelPadding+"px";if(this.value.length>0&&this.border!=null){a.style.borderColor=this.border;a.style.borderWidth="1px";a.style.borderStyle="solid"}else a.style.borderStyle="none"};
-mxText.prototype.getTableSize=function(a){return new mxRectangle(0,0,a.offsetWidth,a.offsetHeight)};
-mxText.prototype.updateTableWidth=function(a){var b=a.firstChild.firstChild.firstChild;if(this.wrap&&this.bounds.width>0&&this.dialect!=mxConstants.DIALECT_SVG){b.style.whiteSpace="nowrap";var c=this.getTableSize(a),c=Math.min(c.width,(this.horizontal||mxUtils.isVml(this.node)?this.bounds.width:this.bounds.height)/this.scale);mxClient.IS_OP&&(c=c*this.scale);a.style.width=Math.round(c)+"px";b.style.whiteSpace="normal"}else a.style.width="";b.style.whiteSpace=this.wrap?"normal":"nowrap"};
-mxText.prototype.redrawVml=function(){this.node.nodeName=="g"?this.redrawForeignObject():mxUtils.isVml(this.node)?this.redrawTextbox():this.redrawHtmlTable()};
-mxText.prototype.redrawTextbox=function(){var a=this.node;a.firstChild==null&&a.appendChild(this.createHtmlTable());var b=a.firstChild;this.updateHtmlTable(b);this.updateTableWidth(b);this.opacity!=null&&mxUtils.setOpacity(b,this.opacity);b.style.filter="";a.inset="0px,0px,0px,0px";if(this.overflow!="fill"){var c=this.getTableSize(b),d=c.width*this.scale,c=c.height*this.scale,e=this.getOffset(this.bounds.width,this.bounds.height,d,c);if(!this.horizontal)b.style.filter=this.ieVerticalFilter;var f=
-this.getSpacing(),b=this.bounds.x-e.x+f.x*this.scale,e=this.bounds.y-e.y+f.y*this.scale,g=this.bounds.x,f=this.bounds.y,h=this.bounds.width,k=this.bounds.height;if(this.horizontal){var i=Math.round(b-g),l=Math.round(e-f),m=Math.min(0,Math.round(g+h-b-d-1)),n=Math.min(0,Math.round(f+k-e-c-1));a.inset=i+"px,"+l+"px,"+m+"px,"+n+"px"}else{n=m=l=i=0;if(this.align==mxConstants.ALIGN_CENTER)n=i=(k-d)/2;else this.align==mxConstants.ALIGN_LEFT?i=k-d:n=k-d;if(this.valign==mxConstants.ALIGN_MIDDLE)m=l=(h-c)/
-2;else this.valign==mxConstants.ALIGN_BOTTOM?l=h-c:m=h-c;a.inset=l+"px,"+i+"px,"+m+"px,"+n+"px"}a.style.zoom=this.scale;if(this.clipped&&this.bounds.width>0&&this.bounds.height>0){this.boundingBox=this.bounds.clone();d=Math.round(g-b);c=Math.round(f-e);a.style.clip="rect("+c/this.scale+" "+(d+this.bounds.width)/this.scale+" "+(c+this.bounds.height)/this.scale+" "+d/this.scale+")"}else this.boundingBox=new mxRectangle(b,e,d,c)}else this.boundingBox=this.bounds.clone()};
-mxText.prototype.redrawHtmlTable=function(){if(!isNaN(this.bounds.x)&&!isNaN(this.bounds.y)&&!isNaN(this.bounds.width)&&!isNaN(this.bounds.height)){var a=this.node,b=a.firstChild.firstChild.firstChild,c=false,d=1;if(mxClient.IS_IE)a.style.removeAttribute("filter");else if(mxClient.IS_SF||mxClient.IS_GC)a.style.WebkitTransform="";else if(mxClient.IS_MT){a.style.MozTransform="";b.style.MozTransform=""}else{if(mxClient.IS_OT)a.style.OTransform="";d=this.scale;c=true}b.style.zoom="";this.updateHtmlTable(a,
-d);this.updateTableWidth(a);this.opacity!=null&&mxUtils.setOpacity(a,this.opacity);a.style.left="";a.style.top="";a.style.height="";var e=parseFloat(b.style.zoom)||1,d=this.bounds.width,f=this.bounds.height;if(!this.forceIgnoreStringSize&&!(this.overflow=="fill"||this.align==mxConstants.ALIGN_LEFT&&this.background==null&&this.border==null)){f=this.getTableSize(a);d=f.width/e;f=f.height/e}var e=this.getOffset(this.bounds.width/this.scale,this.bounds.height/this.scale,d,f,c||this.horizontal),g=this.getSpacing(c||
-this.horizontal),h=this.bounds.x/this.scale-e.x+g.x,k=this.bounds.y/this.scale-e.y+g.y,i=this.scale,g=1,l=0,m=0;if(!this.horizontal)if(mxClient.IS_IE&&mxClient.IS_SVG)a.style.msTransform="rotate("+this.verticalTextDegree+"deg)";else if(mxClient.IS_IE){a.style.filter=this.ieVerticalFilter;l=(d-f)/2;m=-l}else if(mxClient.IS_SF||mxClient.IS_GC)a.style.WebkitTransform="rotate("+this.verticalTextDegree+"deg)";else if(mxClient.IS_OT)a.style.OTransform="rotate("+this.verticalTextDegree+"deg)";else if(mxClient.IS_MT){a.style.MozTransform=
-"rotate("+this.verticalTextDegree+"deg)";b.style.MozTransform="rotate(0deg)";g=1/this.scale;i=1}var n=true;if(mxClient.IS_MT||c)if(mxClient.IS_MT){a.style.MozTransform=a.style.MozTransform+(" scale("+this.scale+")");g=1/this.scale}else{if(mxClient.IS_OT){b.style.OTransform="scale("+this.scale+")";a.style.borderWidth=Math.round(this.scale*parseInt(a.style.borderWidth))+"px"}}else if(!c)if(document.documentMode>=9)b.style.msTransform="scale("+this.scale+")";else if(mxClient.IS_SF||mxClient.IS_GC)b.style.WebkitTransform=
-"scale("+this.scale+")";else{b.style.zoom=this.scale;if(a.style.borderWidth!=""&&document.documentMode!=8)a.style.borderWidth=Math.round(this.scale*parseInt(a.style.borderWidth))+"px";if(document.documentMode==8||!mxClient.IS_IE)i=1;n=false}if(n){l=(this.scale-1)*d/(2*this.scale);m=(this.scale-1)*f/(2*this.scale);i=1}if(this.overflow!="fill"){h=new mxRectangle(Math.round((h+l)*this.scale),Math.round((k+m)*this.scale),Math.round(d*i),Math.round(f*i));a.style.left=h.x+"px";a.style.top=h.y+"px";a.style.width=
-h.width+"px";a.style.height=h.height+"px";if((this.background!=null||this.border!=null)&&document.documentMode>=8){k=this.replaceLinefeeds?this.value.replace(/\n/g,"<br/>"):this.value;b.innerHTML='<div style="padding:'+this.labelPadding+"px;background:"+b.style.background+";border:"+a.style.border+'">'+k+"</div>";b.style.padding="0px";b.style.background="";a.style.border=""}if(this.clipped&&this.bounds.width>0&&this.bounds.height>0){this.boundingBox=this.bounds.clone();if(this.horizontal||c&&!mxClient.IS_OT){b=
-Math.max(0,e.x*i);c=Math.max(0,e.y*i);a.style.clip="rect("+c+"px "+(b+this.bounds.width*g)+"px "+(c+this.bounds.height*g)+"px "+b+"px)"}else if(mxClient.IS_IE){e=this.bounds.width;g=this.bounds.height;c=b=0;this.align==mxConstants.ALIGN_LEFT?b=Math.max(0,d-g/this.scale)*this.scale:this.align==mxConstants.ALIGN_CENTER&&(b=Math.max(0,d-g/this.scale)*this.scale/2);this.valign==mxConstants.ALIGN_BOTTOM?c=Math.max(0,f-e/this.scale)*this.scale:this.valign==mxConstants.ALIGN_MIDDLE&&(c=Math.max(0,f-e/this.scale)*
-this.scale/2);a.style.clip="rect("+b+"px "+(c+e-1)+"px "+(b+g-1)+"px "+c+"px)"}else{e=this.bounds.width/this.scale;g=this.bounds.height/this.scale;if(mxClient.IS_OT){e=this.bounds.width;g=this.bounds.height}c=b=0;this.align==mxConstants.ALIGN_RIGHT?b=Math.max(0,d-g):this.align==mxConstants.ALIGN_CENTER&&(b=Math.max(0,d-g)/2);this.valign==mxConstants.ALIGN_BOTTOM?c=Math.max(0,f-e):this.valign==mxConstants.ALIGN_MIDDLE&&(c=Math.max(0,f-e)/2);if(mxClient.IS_GC||mxClient.IS_SF){b=b*this.scale;c=c*this.scale;
-e=e*this.scale;g=g*this.scale}a.style.clip="rect("+c+" "+(b+g)+" "+(c+e)+" "+b+")"}}else this.boundingBox=h}else{this.boundingBox=this.bounds.clone();if(document.documentMode>=9||mxClient.IS_SVG){a.style.left=Math.round(this.bounds.x+this.scale/2+l)+"px";a.style.top=Math.round(this.bounds.y+this.scale/2+m)+"px";a.style.width=Math.round((this.bounds.width-this.scale)/this.scale)+"px";a.style.height=Math.round((this.bounds.height-this.scale)/this.scale)+"px"}else{i=document.documentMode==8?this.scale:
-1;a.style.left=Math.round(this.bounds.x+this.scale/2)+"px";a.style.top=Math.round(this.bounds.y+this.scale/2)+"px";a.style.width=Math.round((this.bounds.width-this.scale)/i)+"px";a.style.height=Math.round((this.bounds.height-this.scale)/i)+"px"}}}};mxText.prototype.getVerticalOffset=function(a){return new mxPoint(a.y,-a.x)};
-mxText.prototype.redrawForeignObject=function(){for(var a=this.node,b=a.firstChild;b==this.backgroundNode;)b=b.nextSibling;var c=b.firstChild;c.firstChild==null&&c.appendChild(this.createHtmlTable());var d=c.firstChild;this.updateHtmlTable(d);this.opacity!=null&&b.setAttribute("opacity",this.opacity/100);if(mxClient.IS_SF){d.style.borderStyle="none";d.firstChild.firstChild.firstChild.style.background="";if(this.backgroundNode==null&&(this.background!=null||this.border!=null)){this.backgroundNode=
-document.createElementNS(mxConstants.NS_SVG,"rect");a.insertBefore(this.backgroundNode,a.firstChild)}else if(this.backgroundNode!=null&&this.background==null&&this.border==null){this.backgroundNode.parentNode.removeChild(this.backgroundNode);this.backgroundNode=null}if(this.backgroundNode!=null){this.background!=null?this.backgroundNode.setAttribute("fill",this.background):this.backgroundNode.setAttribute("fill","none");this.border!=null?this.backgroundNode.setAttribute("stroke",this.border):this.backgroundNode.setAttribute("stroke",
-"none")}}var e="";if(this.overflow!="fill"){b.removeAttribute("width");b.removeAttribute("height");b.style.width="";b.style.height="";b.style.clip="";(this.wrap||!mxClient.IS_GC&&!mxClient.IS_SF)&&document.body.appendChild(d);this.updateTableWidth(d);var f=this.getTableSize(d),g=f.width,f=f.height;d.parentNode!=c&&c.appendChild(d);var e=this.getSpacing(),h=this.bounds.x/this.scale+e.x,k=this.bounds.y/this.scale+e.y,d=this.bounds.width/this.scale,c=this.bounds.height/this.scale,i=this.getOffset(d,
-c,g,f);if(this.horizontal){h=h-i.x;k=k-i.y;e="scale("+this.scale+")"}else var e="scale("+this.scale+") rotate("+this.verticalTextDegree+" "+(h+g/2)+" "+(k+f/2)+")",l=this.getVerticalOffset(i),h=h+l.x,k=k+l.y;e=e+(" translate("+h+" "+k+")");if(this.backgroundNode!=null){this.backgroundNode.setAttribute("width",g);this.backgroundNode.setAttribute("height",f)}b.setAttribute("width",g);b.setAttribute("height",f);if(this.clipped&&this.bounds.width>0&&this.bounds.height>0){this.boundingBox=this.bounds.clone();
-h=Math.max(0,i.x);i=Math.max(0,i.y);if(this.horizontal)b.style.clip="rect("+i+"px,"+(h+d)+"px,"+(i+c)+"px,"+h+"px)";else{i=h=0;this.align==mxConstants.ALIGN_RIGHT?h=Math.max(0,g-c):this.align==mxConstants.ALIGN_CENTER&&(h=Math.max(0,g-c)/2);this.valign==mxConstants.ALIGN_BOTTOM?i=Math.max(0,f-d):this.valign==mxConstants.ALIGN_MIDDLE&&(i=Math.max(0,f-d)/2);b.style.clip="rect("+i+"px,"+(h+c)+"px,"+(i+d)+"px,"+h+"px)"}if(this.backgroundNode!=null){h=this.bounds.x/this.scale;k=this.bounds.y/this.scale;
-if(!this.horizontal){h=h+((f+g)/2-c);k=k+(f-g)/2;l=d;d=c;c=l}if(!mxClient.IS_GC){b=this.getSvgClip(this.node.ownerSVGElement,h,k,d,c);if(b!=this.clip){this.releaseSvgClip();this.clip=b;b.refCount++}this.backgroundNode.setAttribute("clip-path","url(#"+b.getAttribute("id")+")")}}}else{this.releaseSvgClip();this.backgroundNode!=null&&this.backgroundNode.removeAttribute("clip-path");this.boundingBox=this.horizontal?new mxRectangle(h*this.scale,k*this.scale,g*this.scale,f*this.scale):new mxRectangle(h*
-this.scale,k*this.scale,f*this.scale,g*this.scale)}}else{this.boundingBox=this.bounds.clone();e=this.scale;g=this.bounds.width/e;f=this.bounds.height/e;b.setAttribute("width",g);b.setAttribute("height",f);d.style.width=g+"px";d.style.height=f+"px";if(this.backgroundNode!=null){this.backgroundNode.setAttribute("width",d.clientWidth);this.backgroundNode.setAttribute("height",d.offsetHeight)}e="scale("+e+") translate("+this.bounds.x/e+" "+this.bounds.y/e+")";if(!this.wrap)d.firstChild.firstChild.firstChild.style.whiteSpace=
-"nowrap"}a.setAttribute("transform",e)};
-mxText.prototype.createSvg=function(){var a=document.createElementNS(mxConstants.NS_SVG,"g"),b=this.isStyleSet(mxConstants.FONT_UNDERLINE)?"underline":"none",c=this.isStyleSet(mxConstants.FONT_BOLD)?"bold":"normal",d=this.isStyleSet(mxConstants.FONT_ITALIC)?"italic":null;a.setAttribute("text-decoration",b);a.setAttribute("font-family",this.family);a.setAttribute("font-weight",c);a.setAttribute("font-size",Math.round(this.size*this.scale)+"px");a.setAttribute("fill",this.color);a.setAttribute("text-anchor",
-this.align==mxConstants.ALIGN_RIGHT?"end":this.align==mxConstants.ALIGN_CENTER?"middle":"start");d!=null&&a.setAttribute("font-style",d);if(this.background!=null||this.border!=null){this.backgroundNode=document.createElementNS(mxConstants.NS_SVG,"rect");this.backgroundNode.setAttribute("shape-rendering","crispEdges");this.background!=null?this.backgroundNode.setAttribute("fill",this.background):this.backgroundNode.setAttribute("fill","none");this.border!=null?this.backgroundNode.setAttribute("stroke",
-this.border):this.backgroundNode.setAttribute("stroke","none")}this.updateSvgValue(a);return a};
-mxText.prototype.updateSvgValue=function(a){if(this.currentValue!=this.value){for(;a.firstChild!=null;)a.removeChild(a.firstChild);if(this.value!=null){var b=this.isStyleSet(mxConstants.FONT_UNDERLINE)?"underline":"none",c=this.value.split("\n");this.textNodes=Array(c.length);for(var d=0;d<c.length;d++)if(this.isEmptyString(c[d]))this.textNodes[d]=null;else{var e=this.createSvgSpan(c[d]);a.appendChild(e);this.textNodes[d]=e;e.setAttribute("text-decoration",b)}}this.currentValue=this.value}};
-mxText.prototype.redrawSvg=function(){if(this.node.nodeName=="foreignObject")this.redrawHtml();else{var a=Math.round(this.size*this.scale);a<=0?this.node.setAttribute("visibility","hidden"):this.node.removeAttribute("visibility");this.updateSvgValue(this.node);this.node.setAttribute("font-size",a+"px");if(this.opacity!=null){this.node.setAttribute("fill-opacity",this.opacity/100);this.node.setAttribute("stroke-opacity",this.opacity/100)}var b=this.value,c=this.createHtmlTable();this.lastValue=null;
-this.value=mxUtils.htmlEntities(this.value,false);this.updateHtmlTable(c);document.body.appendChild(c);var d=c.offsetWidth*this.scale,e=c.offsetHeight*this.scale;c.parentNode.removeChild(c);this.value=b;b=2*this.scale;this.align==mxConstants.ALIGN_CENTER?b=b+d/2:this.align==mxConstants.ALIGN_RIGHT&&(b=b+d);var c=Math.round(a*1.3),f=this.textNodes!=null?this.textNodes.length:0,g=this.bounds.x,h=this.bounds.y,g=g+(this.align==mxConstants.ALIGN_RIGHT?(this.horizontal?this.bounds.width:this.bounds.height)-
-this.spacingRight*this.scale:this.align==mxConstants.ALIGN_CENTER?this.spacingLeft*this.scale+((this.horizontal?this.bounds.width:this.bounds.height)-this.spacingLeft*this.scale-this.spacingRight*this.scale)/2:this.spacingLeft*this.scale+1),h=h+(this.valign==mxConstants.ALIGN_BOTTOM?(this.horizontal?this.bounds.height:this.bounds.width)-(f-1)*c-this.spacingBottom*this.scale-4:this.valign==mxConstants.ALIGN_MIDDLE?(this.spacingTop*this.scale+(this.horizontal?this.bounds.height:this.bounds.width)-this.spacingBottom*
-this.scale-(f-1.5)*c)/2:this.spacingTop*this.scale+c);if(this.overflow=="fill"){this.align==mxConstants.ALIGN_CENTER&&(g=Math.max(this.bounds.x+d/2,g));h=Math.max(this.bounds.y+a,h);this.boundingBox=new mxRectangle(g-b,h-c,d+4*this.scale,e+1*this.scale);this.boundingBox.x=Math.min(this.bounds.x,this.boundingBox.x);this.boundingBox.y=Math.min(this.bounds.y,this.boundingBox.y);this.boundingBox.width=Math.max(this.bounds.width,this.boundingBox.width);this.boundingBox.height=Math.max(this.bounds.height,
-this.boundingBox.height)}else this.boundingBox=new mxRectangle(g-b,h-c,d+4*this.scale,e+1*this.scale);this.horizontal||this.node.setAttribute("transform","rotate("+this.verticalTextDegree+" "+(this.bounds.x+this.bounds.width/2)+" "+(this.bounds.y+this.bounds.height/2)+") translate("+-((this.bounds.height-this.bounds.width)/2)+" "+-((this.bounds.width-this.bounds.height)/2)+")");this.redrawSvgTextNodes(g,h,c);if(this.value.length>0&&this.backgroundNode!=null&&this.node.firstChild!=null){this.node.firstChild!=
-this.backgroundNode&&this.node.insertBefore(this.backgroundNode,this.node.firstChild);this.backgroundNode.setAttribute("x",this.boundingBox.x+this.scale/2+1*this.scale);this.backgroundNode.setAttribute("y",this.boundingBox.y+this.scale/2+2*this.scale-this.labelPadding);this.backgroundNode.setAttribute("width",this.boundingBox.width-this.scale-2*this.scale);this.backgroundNode.setAttribute("height",this.boundingBox.height-this.scale);this.backgroundNode.setAttribute("stroke-width",Math.round(Math.max(1,
-this.scale)))}if(this.clipped&&this.bounds.width>0&&this.bounds.height>0){this.boundingBox=this.bounds.clone();if(!this.horizontal){this.boundingBox.width=this.bounds.height;this.boundingBox.height=this.bounds.width}g=this.bounds.x;h=this.bounds.y;if(this.horizontal){d=this.bounds.width;e=this.bounds.height}else{d=this.bounds.height;e=this.bounds.width}a=this.getSvgClip(this.node.ownerSVGElement,g,h,d,e);if(a!=this.clip){this.releaseSvgClip();this.clip=a;a.refCount++}this.node.setAttribute("clip-path",
-"url(#"+a.getAttribute("id")+")")}else{this.releaseSvgClip();this.node.removeAttribute("clip-path")}}};mxText.prototype.redrawSvgTextNodes=function(a,b,c){if(this.textNodes!=null)for(var d=0;d<this.textNodes.length;d++){var e=this.textNodes[d];if(e!=null){e.setAttribute("x",a);e.setAttribute("y",b);e.setAttribute("style","pointer-events: all")}b=b+c}};
-mxText.prototype.releaseSvgClip=function(){if(this.clip!=null){this.clip.refCount--;this.clip.refCount==0&&this.clip.parentNode.removeChild(this.clip);this.clip=null}};
-mxText.prototype.getSvgClip=function(a,b,c,d,e){var b=Math.round(b),c=Math.round(c),d=Math.round(d),e=Math.round(e),f="mx-clip-"+b+"-"+c+"-"+d+"-"+e;if(this.clip!=null&&this.clip.ident==f)return this.clip;var g=0,h;for(h=document.getElementById(f+"-"+g);h!=null;){if(h.ownerSVGElement==a)return h;g++;h=f+"-"+g;h=document.getElementById(h)}if(h!=null){h=h.cloneNode(true);g++}else{h=document.createElementNS(mxConstants.NS_SVG,"clipPath");var k=document.createElementNS(mxConstants.NS_SVG,"rect");k.setAttribute("x",
-b);k.setAttribute("y",c);k.setAttribute("width",d);k.setAttribute("height",e);h.appendChild(k)}h.setAttribute("id",f+"-"+g);h.ident=f;a.appendChild(h);h.refCount=0;return h};mxText.prototype.isEmptyString=function(a){return a.replace(/ /g,"").length==0};mxText.prototype.createSvgSpan=function(a){var b=document.createElementNS(mxConstants.NS_SVG,"text");mxUtils.write(b,a);return b};mxText.prototype.destroy=function(){this.releaseSvgClip();mxShape.prototype.destroy.apply(this,arguments)};
-function mxTriangle(){}mxTriangle.prototype=new mxActor;mxTriangle.prototype.constructor=mxTriangle;mxTriangle.prototype.redrawPath=function(a,b,c,d,e){a.moveTo(0,0);a.lineTo(d,0.5*e);a.lineTo(0,e);a.close()};function mxHexagon(){}mxHexagon.prototype=new mxActor;mxHexagon.prototype.constructor=mxHexagon;mxHexagon.prototype.redrawPath=function(a,b,c,d,e){a.moveTo(0.25*d,0);a.lineTo(0.75*d,0);a.lineTo(d,0.5*e);a.lineTo(0.75*d,e);a.lineTo(0.25*d,e);a.lineTo(0,0.5*e);a.close()};
-function mxLine(a,b,c){this.bounds=a;this.stroke=b;this.strokewidth=c!=null?c:1}mxLine.prototype=new mxShape;mxLine.prototype.constructor=mxLine;mxLine.prototype.vmlNodes=mxLine.prototype.vmlNodes.concat(["label","innerNode"]);mxLine.prototype.mixedModeHtml=!1;mxLine.prototype.preferModeHtml=!1;mxLine.prototype.clone=function(){var a=new mxLine(this.bounds,this.stroke,this.strokewidth);a.isDashed=this.isDashed;return a};
-mxLine.prototype.createVml=function(){var a=document.createElement("v:group");a.style.position="absolute";this.label=document.createElement("v:rect");this.label.style.position="absolute";this.label.stroked="false";this.label.filled="false";a.appendChild(this.label);this.innerNode=document.createElement("v:shape");this.configureVmlShape(this.innerNode);a.appendChild(this.innerNode);return a};
-mxLine.prototype.reconfigure=function(){mxUtils.isVml(this.node)?this.configureVmlShape(this.innerNode):mxShape.prototype.reconfigure.apply(this,arguments)};
-mxLine.prototype.redrawVml=function(){this.updateVmlShape(this.node);this.updateVmlShape(this.label);this.innerNode.coordsize=this.node.coordsize;this.innerNode.strokeweight=this.strokewidth*this.scale+"px";this.innerNode.style.width=this.node.style.width;this.innerNode.style.height=this.node.style.height;var a=this.bounds.width,b=this.bounds.height;this.innerNode.path=this.direction==mxConstants.DIRECTION_NORTH||this.direction==mxConstants.DIRECTION_SOUTH?"m "+Math.round(a/2)+" 0 l "+Math.round(a/
-2)+" "+Math.round(b)+" e":"m 0 "+Math.round(b/2)+" l "+Math.round(a)+" "+Math.round(b/2)+" e"};mxLine.prototype.createSvg=function(){var a=this.createSvgGroup("path");this.pipe=this.createSvgPipe();a.appendChild(this.pipe);return a};
-mxLine.prototype.redrawSvg=function(){this.innerNode.setAttribute("stroke-width",Math.round(Math.max(1,this.strokewidth*this.scale)));if(this.bounds!=null){var a=this.bounds.x,b=this.bounds.y,c=this.bounds.width,d=this.bounds.height,e=null,e=this.direction==mxConstants.DIRECTION_NORTH||this.direction==mxConstants.DIRECTION_SOUTH?"M "+Math.round(a+c/2)+" "+Math.round(b)+" L "+Math.round(a+c/2)+" "+Math.round(b+d):"M "+Math.round(a)+" "+Math.round(b+d/2)+" L "+Math.round(a+c)+" "+Math.round(b+d/2);
-this.innerNode.setAttribute("d",e);this.pipe.setAttribute("d",e);this.pipe.setAttribute("stroke-width",this.strokewidth+mxShape.prototype.SVG_STROKE_TOLERANCE);this.updateSvgTransform(this.innerNode,false);this.updateSvgTransform(this.pipe,false);this.crisp?this.innerNode.setAttribute("shape-rendering","crispEdges"):this.innerNode.removeAttribute("shape-rendering");if(this.isDashed){a=Math.max(1,Math.round(3*this.scale*this.strokewidth));this.innerNode.setAttribute("stroke-dasharray",a+" "+a)}}};
-function mxImageShape(a,b,c,d,e){this.bounds=a;this.image=b!=null?b:"";this.fill=c;this.stroke=d;this.strokewidth=e!=null?e:1;this.isShadow=false}mxImageShape.prototype=new mxShape;mxImageShape.prototype.constructor=mxImageShape;mxImageShape.prototype.crisp=!1;mxImageShape.prototype.preserveImageAspect=!0;
-mxImageShape.prototype.apply=function(a){mxShape.prototype.apply.apply(this,arguments);this.stroke=this.fill=null;if(this.style!=null){this.fill=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_BACKGROUND);this.stroke=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_BORDER);this.preserveImageAspect=mxUtils.getNumber(this.style,mxConstants.STYLE_IMAGE_ASPECT,1)==1;this.gradient=null}};
-mxImageShape.prototype.create=function(){var a=null;if(this.dialect==mxConstants.DIALECT_SVG){a=this.createSvgGroup("rect");this.innerNode.setAttribute("visibility","hidden");this.innerNode.setAttribute("pointer-events","fill");this.imageNode=document.createElementNS(mxConstants.NS_SVG,"image");this.imageNode.setAttributeNS(mxConstants.NS_XLINK,"xlink:href",this.image);this.imageNode.setAttribute("style","pointer-events:none");this.configureSvgShape(this.imageNode);this.imageNode.removeAttribute("stroke");
-this.imageNode.removeAttribute("fill");a.insertBefore(this.imageNode,this.innerNode);if(this.fill!=null&&this.fill!=mxConstants.NONE||this.stroke!=null&&this.stroke!=mxConstants.NONE){this.bg=document.createElementNS(mxConstants.NS_SVG,"rect");a.insertBefore(this.bg,a.firstChild)}this.preserveImageAspect||this.imageNode.setAttribute("preserveAspectRatio","none")}else{var a=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_FLIPH,0)==1,b=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_FLIPV,0)==
-1,c=this.image.toUpperCase();if(mxClient.IS_IE&&!a&&!b&&c.substring(0,6)=="MHTML:"){this.imageNode=document.createElement("DIV");this.imageNode.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader (src='"+this.image+"', sizingMethod='scale')";a=document.createElement("DIV");this.configureHtmlShape(a)}else if(!mxClient.IS_IE||c.substring(0,5)=="DATA:"||document.documentMode>=9){this.imageNode=document.createElement("img");this.imageNode.setAttribute("src",this.image);this.imageNode.setAttribute("border",
-"0");this.imageNode.style.position="absolute";this.imageNode.style.width="100%";this.imageNode.style.height="100%";a=document.createElement("DIV");this.configureHtmlShape(a)}else{this.imageNode=document.createElement("v:image");this.imageNode.style.position="absolute";this.imageNode.src=this.image;a=document.createElement("DIV");this.configureHtmlShape(a);a.style.overflow="visible"}a.appendChild(this.imageNode)}return a};
-mxImageShape.prototype.updateAspect=function(a,b){var c=Math.min(this.bounds.width/a,this.bounds.height/b),a=Math.max(0,Math.round(a*c)),b=Math.max(0,Math.round(b*c)),c=Math.max(0,Math.round((this.bounds.width-a)/2)),d=Math.max(0,Math.round((this.bounds.height-b)/2)),e=this.imageNode.style;if(this.imageNode.parentNode==this.node){this.node.style.paddingLeft=c+"px";this.node.style.paddingTop=d+"px"}else{e.left=Math.round(this.bounds.x)+c+"px";e.top=Math.round(this.bounds.y)+d+"px"}e.width=a+"px";e.height=
-b+"px"};mxImageShape.prototype.scheduleUpdateAspect=function(){var a=new Image;a.onload=mxUtils.bind(this,function(){mxImageShape.prototype.updateAspect.call(this,a.width,a.height)});a.src=this.image};
-mxImageShape.prototype.redraw=function(){mxShape.prototype.redraw.apply(this,arguments);if(this.imageNode!=null&&this.bounds!=null){var a=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_FLIPH,0)==1,b=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_FLIPV,0)==1;if(this.dialect==mxConstants.DIALECT_SVG){var c=1,d=1,e=0,f=0;if(a){c=-1;e=-this.bounds.width-2*this.bounds.x}if(b){d=-1;f=-this.bounds.height-2*this.bounds.y}this.imageNode.setAttribute("transform",(this.imageNode.getAttribute("transform")||
-"")+" scale("+c+" "+d+") translate("+e+" "+f+")")}else{if(this.imageNode.nodeName!="DIV"){this.imageNode.style.width=Math.max(0,Math.round(this.bounds.width))+"px";this.imageNode.style.height=Math.max(0,Math.round(this.bounds.height))+"px"}this.preserveImageAspect&&this.scheduleUpdateAspect();if(a||b)if(mxUtils.isVml(this.imageNode))a&&b?this.imageNode.style.rotation="180":this.imageNode.style.flip=a?"x":"y";else{c=this.imageNode.nodeName=="DIV"?"progid:DXImageTransform.Microsoft.AlphaImageLoader (src='"+
-this.image+"', sizingMethod='scale')":"";c=a&&b?c+"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)":a?c+"progid:DXImageTransform.Microsoft.BasicImage(mirror=1)":c+"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";if(this.imageNode.style.filter!=c)this.imageNode.style.filter=c}}}};mxImageShape.prototype.configureTransparentBackground=function(){};
-mxImageShape.prototype.redrawSvg=function(){this.updateSvgShape(this.innerNode);this.updateSvgShape(this.imageNode);if(this.bg!=null){this.updateSvgShape(this.bg);this.fill!=null?this.bg.setAttribute("fill",this.fill):this.bg.setAttribute("fill","none");this.stroke!=null?this.bg.setAttribute("stroke",this.stroke):this.bg.setAttribute("stroke","none");this.bg.setAttribute("shape-rendering","crispEdges")}};
-mxImageShape.prototype.configureSvgShape=function(a){mxShape.prototype.configureSvgShape.apply(this,arguments);this.imageNode!=null&&(this.opacity!=null?this.imageNode.setAttribute("opacity",this.opacity/100):this.imageNode.removeAttribute("opacity"))};function mxLabel(a,b,c,d){this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=d!=null?d:1}mxLabel.prototype=new mxShape;mxLabel.prototype.constructor=mxLabel;
-mxLabel.prototype.vmlNodes=mxLabel.prototype.vmlNodes.concat(["label","imageNode","indicatorImageNode","rectNode"]);mxLabel.prototype.imageSize=mxConstants.DEFAULT_IMAGESIZE;mxLabel.prototype.spacing=2;mxLabel.prototype.indicatorSize=10;mxLabel.prototype.indicatorSpacing=2;mxLabel.prototype.opaqueVmlImages=!1;
-mxLabel.prototype.init=function(a){mxShape.prototype.init.apply(this,arguments);if(this.indicatorColor!=null&&this.indicatorShape!=null){this.indicator=new this.indicatorShape;this.indicator.dialect=this.dialect;this.indicator.bounds=this.bounds;this.indicator.fill=this.indicatorColor;this.indicator.stroke=this.indicatorColor;this.indicator.gradient=this.indicatorGradientColor;this.indicator.direction=this.indicatorDirection;this.indicator.init(this.node);this.indicatorShape=null}};
-mxLabel.prototype.reconfigure=function(){mxShape.prototype.reconfigure.apply(this);if(this.indicator!=null){this.indicator.fill=this.indicatorColor;this.indicator.stroke=this.indicatorColor;this.indicator.gradient=this.indicatorGradientColor;this.indicator.direction=this.indicatorDirection;this.indicator.reconfigure()}};
-mxLabel.prototype.createHtml=function(){var a=document.createElement("DIV");this.configureHtmlShape(a);if(this.indicatorImage!=null){this.indicatorImageNode=mxUtils.createImage(this.indicatorImage);this.indicatorImageNode.style.position="absolute";a.appendChild(this.indicatorImageNode)}if(this.image!=null){this.imageNode=mxUtils.createImage(this.image);this.stroke=null;this.configureHtmlShape(this.imageNode);mxUtils.setOpacity(this.imageNode,"100");a.appendChild(this.imageNode)}return a};
-mxLabel.prototype.createVml=function(){var a=document.createElement("v:group");this.rectNode=document.createElement(this.isRounded?"v:roundrect":"v:rect");this.configureVmlShape(this.rectNode);this.isShadow=false;this.configureVmlShape(a);a.coordorigin="0,0";a.appendChild(this.rectNode);if(this.indicatorImage!=null){this.indicatorImageNode=this.createVmlImage(this.indicatorImage,this.opaqueVmlImages?null:this.opacity);a.appendChild(this.indicatorImageNode)}if(this.image!=null){this.imageNode=this.createVmlImage(this.image,
-this.opaqueVmlImages?null:this.opacity);a.appendChild(this.imageNode)}this.label=document.createElement("v:rect");this.label.style.top="0px";this.label.style.left="0px";this.label.filled="false";this.label.stroked="false";a.appendChild(this.label);return a};
-mxLabel.prototype.createVmlImage=function(a,b){var c=null;if(a.substring(0,5)=="data:"||b!=null){c=document.createElement("img");mxUtils.setOpacity(c,b);c.setAttribute("border","0");c.style.position="absolute";c.setAttribute("src",a)}else{c=document.createElement("v:image");c.src=a}return c};
-mxLabel.prototype.createSvg=function(){var a=this.createSvgGroup("rect");if(this.indicatorImage!=null){this.indicatorImageNode=document.createElementNS(mxConstants.NS_SVG,"image");this.indicatorImageNode.setAttributeNS(mxConstants.NS_XLINK,"href",this.indicatorImage);a.appendChild(this.indicatorImageNode);this.opacity!=null&&this.indicatorImageNode.setAttribute("opacity",this.opacity/100)}if(this.image!=null){this.imageNode=document.createElementNS(mxConstants.NS_SVG,"image");this.imageNode.setAttributeNS(mxConstants.NS_XLINK,
-"href",this.image);this.opacity!=null&&this.imageNode.setAttribute("opacity",this.opacity/100);this.imageNode.setAttribute("style","pointer-events:none");this.configureSvgShape(this.imageNode);a.appendChild(this.imageNode)}return a};
-mxLabel.prototype.redraw=function(){this.updateBoundingBox();var a=this.dialect==mxConstants.DIALECT_SVG,b=mxUtils.isVml(this.node);if(a){this.updateSvgShape(this.innerNode);this.shadowNode!=null&&this.updateSvgShape(this.shadowNode);this.updateSvgGlassPane()}else if(b){this.updateVmlShape(this.node);this.updateVmlShape(this.rectNode);this.label.style.width=this.node.style.width;this.label.style.height=this.node.style.height;this.updateVmlGlassPane()}else this.updateHtmlShape(this.node);var c=b=0;
-if(this.imageNode!=null){b=(this.style[mxConstants.STYLE_IMAGE_WIDTH]||this.imageSize)*this.scale;c=(this.style[mxConstants.STYLE_IMAGE_HEIGHT]||this.imageSize)*this.scale}var d=0,e=0,f=0;if(this.indicator!=null||this.indicatorImageNode!=null){d=(this.style[mxConstants.STYLE_INDICATOR_SPACING]||this.indicatorSpacing)*this.scale;e=(this.style[mxConstants.STYLE_INDICATOR_WIDTH]||this.indicatorSize)*this.scale;f=(this.style[mxConstants.STYLE_INDICATOR_HEIGHT]||this.indicatorSize)*this.scale}var g=this.style[mxConstants.STYLE_IMAGE_ALIGN],
-h=this.style[mxConstants.STYLE_IMAGE_VERTICAL_ALIGN],k=this.spacing*this.scale+5,i=Math.max(b,e),l=c+d+f,m=a?this.bounds.x:0,m=g==mxConstants.ALIGN_RIGHT?m+(this.bounds.width-i-k):g==mxConstants.ALIGN_CENTER?m+(this.bounds.width-i)/2:m+k,g=a?this.bounds.y:0,g=h==mxConstants.ALIGN_BOTTOM?g+(this.bounds.height-l-k):h==mxConstants.ALIGN_TOP?g+k:g+(this.bounds.height-l)/2;if(this.imageNode!=null)if(a){this.imageNode.setAttribute("x",m+(i-b)/2+"px");this.imageNode.setAttribute("y",g+"px");this.imageNode.setAttribute("width",
-b+"px");this.imageNode.setAttribute("height",c+"px")}else{this.imageNode.style.left=m+i-b+"px";this.imageNode.style.top=g+"px";this.imageNode.style.width=b+"px";this.imageNode.style.height=c+"px";this.imageNode.stroked="false"}if(this.indicator!=null){this.indicator.bounds=new mxRectangle(m+(i-e)/2,g+c+d,e,f);this.indicator.redraw()}else if(this.indicatorImageNode!=null)if(a){this.indicatorImageNode.setAttribute("x",m+(i-e)/2+"px");this.indicatorImageNode.setAttribute("y",g+c+d+"px");this.indicatorImageNode.setAttribute("width",
-e+"px");this.indicatorImageNode.setAttribute("height",f+"px")}else{this.indicatorImageNode.style.left=m+(i-e)/2+"px";this.indicatorImageNode.style.top=g+c+d+"px";this.indicatorImageNode.style.width=e+"px";this.indicatorImageNode.style.height=f+"px"}};function mxCylinder(a,b,c,d){this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=d!=null?d:1}mxCylinder.prototype=new mxShape;mxCylinder.prototype.constructor=mxCylinder;
-mxCylinder.prototype.vmlNodes=mxCylinder.prototype.vmlNodes.concat(["background","foreground"]);mxCylinder.prototype.mixedModeHtml=!1;mxCylinder.prototype.preferModeHtml=!1;mxCylinder.prototype.addPipe=!1;mxCylinder.prototype.strokedBackground=!0;mxCylinder.prototype.maxHeight=40;mxCylinder.prototype.vmlScale=2;mxCylinder.prototype.create=function(a){if(this.stroke==null)this.stroke=this.fill;return mxShape.prototype.create.apply(this,arguments)};
-mxCylinder.prototype.reconfigure=function(){if(this.dialect==mxConstants.DIALECT_SVG){this.configureSvgShape(this.foreground);this.foreground.setAttribute("fill","none")}else if(mxUtils.isVml(this.node)){this.configureVmlShape(this.background);this.configureVmlShape(this.foreground)}mxShape.prototype.reconfigure.apply(this)};
-mxCylinder.prototype.createVml=function(){var a=document.createElement("v:group");this.label=this.background=document.createElement("v:shape");this.configureVmlShape(this.background);a.appendChild(this.background);this.fill=null;this.isShadow=false;this.configureVmlShape(a);this.foreground=document.createElement("v:shape");this.configureVmlShape(this.foreground);this.fgStrokeNode=document.createElement("v:stroke");this.fgStrokeNode.joinstyle="miter";this.fgStrokeNode.miterlimit=4;this.foreground.appendChild(this.fgStrokeNode);
-a.appendChild(this.foreground);return a};mxCylinder.prototype.redrawVml=function(){this.updateVmlShape(this.node);this.updateVmlShape(this.background);this.updateVmlShape(this.foreground);this.background.path=this.createPath(false);this.foreground.path=this.createPath(true);this.fgStrokeNode.dashstyle=this.strokeNode.dashstyle};
-mxCylinder.prototype.createSvg=function(){var a=this.createSvgGroup("path");this.foreground=document.createElementNS(mxConstants.NS_SVG,"path");this.stroke!=null&&this.stroke!=mxConstants.NONE?this.foreground.setAttribute("stroke",this.stroke):this.foreground.setAttribute("stroke","none");this.foreground.setAttribute("fill","none");a.appendChild(this.foreground);if(this.addPipe){this.pipe=this.createSvgPipe();a.appendChild(this.pipe)}return a};
-mxCylinder.prototype.redrawSvg=function(){var a=Math.round(Math.max(1,this.strokewidth*this.scale));this.innerNode.setAttribute("stroke-width",a);if(this.crisp&&(this.rotation==null||this.rotation==0)){this.innerNode.setAttribute("shape-rendering","crispEdges");this.foreground.setAttribute("shape-rendering","crispEdges")}else{this.innerNode.removeAttribute("shape-rendering");this.foreground.removeAttribute("shape-rendering")}var b=this.createPath(false);if(b.length>0){this.innerNode.setAttribute("d",
-b);if(this.pipe!=null){this.pipe.setAttribute("d",b);this.pipe.setAttribute("stroke-width",a+mxShape.prototype.SVG_STROKE_TOLERANCE);this.pipe.setAttribute("transform",this.innerNode.getAttribute("transform")||"")}}else{this.innerNode.removeAttribute("d");this.pipe!=null&&this.pipe.removeAttribute("d")}this.strokedBackground||this.innerNode.setAttribute("stroke","none");if(this.shadowNode!=null){this.shadowNode.setAttribute("stroke-width",a);this.shadowNode.setAttribute("d",b);this.shadowNode.setAttribute("transform",
-this.getSvgShadowTransform())}b=this.createPath(true);if(b.length>0){this.foreground.setAttribute("stroke-width",a);this.foreground.setAttribute("d",b)}else this.foreground.removeAttribute("d");if(this.isDashed){a=Math.max(1,Math.round(3*this.scale*this.strokewidth));this.innerNode.setAttribute("stroke-dasharray",a+" "+a);this.foreground.setAttribute("stroke-dasharray",a+" "+a)}};
-mxCylinder.prototype.redrawPath=function(a,b,c,d,e,f){b=Math.min(this.maxHeight,Math.round(e/5));if(f){a.moveTo(0,b);a.curveTo(0,2*b,d,2*b,d,b)}else{a.moveTo(0,b);a.curveTo(0,-b/3,d,-b/3,d,b);a.lineTo(d,e-b);a.curveTo(d,e+b/3,0,e+b/3,0,e-b);a.close()}};function mxConnector(a,b,c){this.points=a;this.stroke=b;this.strokewidth=c!=null?c:1}mxConnector.prototype=new mxShape;mxConnector.prototype.constructor=mxConnector;mxConnector.prototype.vmlNodes=mxConnector.prototype.vmlNodes.concat("shapeNode start end startStroke endStroke startFill endFill".split(" "));
-mxConnector.prototype.mixedModeHtml=!1;mxConnector.prototype.preferModeHtml=!1;mxConnector.prototype.allowCrispMarkers=!1;mxConnector.prototype.addPipe=!0;mxConnector.prototype.configureHtmlShape=function(a){mxShape.prototype.configureHtmlShape.apply(this,arguments);a.style.borderStyle="";a.style.background=""};
-mxConnector.prototype.createVml=function(){var a=document.createElement("v:group");a.style.position="absolute";this.shapeNode=document.createElement("v:shape");this.updateVmlStrokeColor(this.shapeNode);this.updateVmlStrokeNode(this.shapeNode);a.appendChild(this.shapeNode);this.shapeNode.filled="false";this.isShadow&&this.createVmlShadow(this.shapeNode);if(this.startArrow!=null){this.start=document.createElement("v:shape");this.start.style.position="absolute";this.startStroke=document.createElement("v:stroke");
-this.startStroke.joinstyle="miter";this.start.appendChild(this.startStroke);this.startFill=document.createElement("v:fill");this.start.appendChild(this.startFill);a.appendChild(this.start)}if(this.endArrow!=null){this.end=document.createElement("v:shape");this.end.style.position="absolute";this.endStroke=document.createElement("v:stroke");this.endStroke.joinstyle="miter";this.end.appendChild(this.endStroke);this.endFill=document.createElement("v:fill");this.end.appendChild(this.endFill);a.appendChild(this.end)}this.updateVmlMarkerOpacity();
-return a};mxConnector.prototype.updateVmlMarkerOpacity=function(){var a=this.opacity!=null?this.opacity+"%":"100%";if(this.start!=null){this.startFill.opacity=a;this.startStroke.opacity=a}if(this.end!=null){this.endFill.opacity=a;this.endStroke.opacity=a}};
-mxConnector.prototype.reconfigure=function(){this.fill=null;if(mxUtils.isVml(this.node)){this.node.style.visibility="hidden";this.configureVmlShape(this.shapeNode);this.updateVmlMarkerOpacity();this.node.style.visibility="visible"}else mxShape.prototype.reconfigure.apply(this,arguments)};
-mxConnector.prototype.redrawVml=function(){if(this.node!=null&&this.points!=null&&this.bounds!=null&&!isNaN(this.bounds.x)&&!isNaN(this.bounds.y)&&!isNaN(this.bounds.width)&&!isNaN(this.bounds.height)){var a=Math.max(0,Math.round(this.bounds.width)),b=Math.max(0,Math.round(this.bounds.height)),c=a+","+b,a=a+"px",b=b+"px";if(this.start!=null){this.start.style.width=a;this.start.style.height=b;this.start.coordsize=c;var d=this.points[1],e=this.points[0],f=mxUtils.getNumber(this.style,mxConstants.STYLE_STARTSIZE,
-mxConstants.DEFAULT_MARKERSIZE);this.startOffset=this.redrawMarker(this.start,this.startArrow,d,e,this.stroke,f)}if(this.end!=null){this.end.style.width=a;this.end.style.height=b;this.end.coordsize=c;a=this.points.length;d=this.points[a-2];e=this.points[a-1];f=mxUtils.getNumber(this.style,mxConstants.STYLE_ENDSIZE,mxConstants.DEFAULT_MARKERSIZE);this.endOffset=this.redrawMarker(this.end,this.endArrow,d,e,this.stroke,f)}this.updateVmlShape(this.node);this.updateVmlShape(this.shapeNode);this.shapeNode.filled=
-"false";if(this.isDashed){d=mxUtils.getValue(this.style,"dashStyle",null);if(d!=null)this.strokeNode.dashstyle=d;if(this.shadowStrokeNode!=null)this.shadowStrokeNode.dashstyle=this.strokeNode.dashstyle}}};
-mxConnector.prototype.createSvg=function(){this.fill=null;var a=this.createSvgGroup("path");if(this.startArrow!=null){this.start=document.createElementNS(mxConstants.NS_SVG,"path");a.appendChild(this.start)}if(this.endArrow!=null){this.end=document.createElementNS(mxConstants.NS_SVG,"path");a.appendChild(this.end)}if(this.addPipe){this.pipe=this.createSvgPipe();a.appendChild(this.pipe)}return a};
-mxConnector.prototype.redrawSvg=function(){if(this.points!=null&&this.points[0]!=null){var a=this.innerNode.getAttribute("stroke");if(this.start!=null){var b=this.points[1],c=this.points[0],d=mxUtils.getNumber(this.style,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_MARKERSIZE);this.startOffset=this.redrawMarker(this.start,this.startArrow,b,c,a,d);this.allowCrispMarkers&&this.crisp?this.start.setAttribute("shape-rendering","crispEdges"):this.start.removeAttribute("shape-rendering")}if(this.end!=
-null){c=this.points.length;b=this.points[c-2];c=this.points[c-1];d=mxUtils.getNumber(this.style,mxConstants.STYLE_ENDSIZE,mxConstants.DEFAULT_MARKERSIZE);this.endOffset=this.redrawMarker(this.end,this.endArrow,b,c,a,d);this.allowCrispMarkers&&this.crisp?this.end.setAttribute("shape-rendering","crispEdges"):this.end.removeAttribute("shape-rendering")}}this.updateSvgShape(this.innerNode);a=this.innerNode.getAttribute("d");if(a!=null){b=Math.round(this.strokewidth*this.scale);if(this.pipe!=null){this.pipe.setAttribute("d",
-this.innerNode.getAttribute("d"));this.pipe.setAttribute("stroke-width",b+mxShape.prototype.SVG_STROKE_TOLERANCE)}if(this.shadowNode!=null){this.shadowNode.setAttribute("transform",this.getSvgShadowTransform());this.shadowNode.setAttribute("d",a);this.shadowNode.setAttribute("stroke-width",b)}}if(this.isDashed){a=this.createDashPattern(this.scale*this.strokewidth);a!=null&&this.innerNode.setAttribute("stroke-dasharray",a)}if(this.shadowNode!=null){a=this.innerNode.getAttribute("stroke-dasharray");
-a!=null&&this.shadowNode.setAttribute("stroke-dasharray",a)}};mxConnector.prototype.createDashPattern=function(a){var b=mxUtils.getValue(this.style,"dashPattern",null);if(b!=null){for(var b=b.split(" "),c=[],d=0;d<b.length;d++)b[d].length>0&&c.push(Math.round(Number(b[d])*a));return c.join(" ")}return null};mxConnector.prototype.redrawMarker=function(a,b,c,d,e,f){return mxMarker.paintMarker(a,b,c,d,e,this.strokewidth,f,this.scale,this.bounds.x,this.bounds.y,this.start==a,this.style)};
-function mxSwimlane(a,b,c,d){this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=d!=null?d:1}mxSwimlane.prototype=new mxShape;mxSwimlane.prototype.constructor=mxSwimlane;mxSwimlane.prototype.vmlNodes=mxSwimlane.prototype.vmlNodes.concat(["label","content","imageNode","separator"]);mxSwimlane.prototype.imageSize=16;mxSwimlane.prototype.mixedModeHtml=!1;mxRhombus.prototype.preferModeHtml=!1;
-mxSwimlane.prototype.createHtml=function(){var a=document.createElement("DIV");this.configureHtmlShape(a);a.style.background="";a.style.backgroundColor="";a.style.borderStyle="none";this.label=document.createElement("DIV");this.configureHtmlShape(this.label);a.appendChild(this.label);this.content=document.createElement("DIV");this.configureHtmlShape(this.content);this.content.style.backgroundColor="";mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,true)?this.content.style.borderTopStyle=
-"none":this.content.style.borderLeftStyle="none";this.content.style.cursor="default";a.appendChild(this.content);var b=this.style[mxConstants.STYLE_SEPARATORCOLOR];if(b!=null){this.separator=document.createElement("DIV");this.separator.style.borderColor=b;this.separator.style.borderLeftStyle="dashed";a.appendChild(this.separator)}if(this.image!=null){this.imageNode=mxUtils.createImage(this.image);this.configureHtmlShape(this.imageNode);this.imageNode.style.borderStyle="none";a.appendChild(this.imageNode)}return a};
-mxSwimlane.prototype.reconfigure=function(a){mxShape.prototype.reconfigure.apply(this,arguments);if(this.dialect==mxConstants.DIALECT_SVG){if(this.shadowNode!=null){this.updateSvgShape(this.shadowNode);mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,true)?this.shadowNode.setAttribute("height",this.startSize*this.scale):this.shadowNode.setAttribute("width",this.startSize*this.scale)}}else if(!mxUtils.isVml(this.node)){this.node.style.background="";this.node.style.backgroundColor=""}};
-mxSwimlane.prototype.redrawHtml=function(){this.updateHtmlShape(this.node);this.node.style.background="";this.node.style.backgroundColor="";this.startSize=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE));this.updateHtmlShape(this.label);this.label.style.top="0px";this.label.style.left="0px";if(mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,true)){this.startSize=Math.min(this.startSize,this.bounds.height);this.label.style.height=this.startSize*
-this.scale+"px";this.updateHtmlShape(this.content);this.content.style.background="";this.content.style.backgroundColor="";var a=this.startSize*this.scale;this.content.style.top=a+"px";this.content.style.left="0px";this.content.style.height=Math.max(1,this.bounds.height-a)+"px";if(this.separator!=null){this.separator.style.left=Math.round(this.bounds.width)+"px";this.separator.style.top=Math.round(this.startSize*this.scale)+"px";this.separator.style.width="1px";this.separator.style.height=Math.round(this.bounds.height)+
-"px";this.separator.style.borderWidth=Math.round(this.scale)+"px"}if(this.imageNode!=null){this.imageNode.style.left=this.bounds.width-this.imageSize-4+"px";this.imageNode.style.top="0px";this.imageNode.style.width=Math.round(this.imageSize*this.scale)+"px";this.imageNode.style.height=Math.round(this.imageSize*this.scale)+"px"}}else{this.startSize=Math.min(this.startSize,this.bounds.width);this.label.style.width=this.startSize*this.scale+"px";this.updateHtmlShape(this.content);this.content.style.background=
-"";this.content.style.backgroundColor="";a=this.startSize*this.scale;this.content.style.top="0px";this.content.style.left=a+"px";this.content.style.width=Math.max(0,this.bounds.width-a)+"px";if(this.separator!=null){this.separator.style.left=Math.round(this.startSize*this.scale)+"px";this.separator.style.top=Math.round(this.bounds.height)+"px";this.separator.style.width=Math.round(this.bounds.width)+"px";this.separator.style.height="1px"}if(this.imageNode!=null){this.imageNode.style.left=this.bounds.width-
-this.imageSize-4+"px";this.imageNode.style.top="0px";this.imageNode.style.width=this.imageSize*this.scale+"px";this.imageNode.style.height=this.imageSize*this.scale+"px"}}};
-mxSwimlane.prototype.createVml=function(){var a=document.createElement("v:group"),b=this.isRounded?"v:roundrect":"v:rect";this.label=document.createElement(b);this.configureVmlShape(this.label);this.isRounded&&this.label.setAttribute("arcsize","20%");this.isShadow=false;this.configureVmlShape(a);a.coordorigin="0,0";a.appendChild(this.label);this.content=document.createElement(b);b=this.fill;this.fill=null;this.configureVmlShape(this.content);a.style.background="";this.isRounded&&this.content.setAttribute("arcsize",
-"4%");this.fill=b;this.content.style.borderBottom="0px";a.appendChild(this.content);b=this.style[mxConstants.STYLE_SEPARATORCOLOR];if(b!=null){this.separator=document.createElement("v:shape");this.separator.style.position="absolute";this.separator.strokecolor=b;b=document.createElement("v:stroke");b.dashstyle="2 2";this.separator.appendChild(b);a.appendChild(this.separator)}if(this.image!=null){this.imageNode=document.createElement("v:image");this.imageNode.src=this.image;this.configureVmlShape(this.imageNode);
-this.imageNode.stroked="false";a.appendChild(this.imageNode)}return a};
-mxSwimlane.prototype.redrawVml=function(){var a=Math.round(this.bounds.x),b=Math.round(this.bounds.y),c=Math.round(this.bounds.width),d=Math.round(this.bounds.height);this.updateVmlShape(this.node);this.node.coordsize=c+","+d;this.updateVmlShape(this.label);this.label.style.top="0px";this.label.style.left="0px";this.label.style.rotation=null;this.startSize=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE));var e=Math.round(this.startSize*this.scale);if(this.separator!=
-null){this.separator.coordsize=c+","+d;this.separator.style.left=a+"px";this.separator.style.top=b+"px";this.separator.style.width=c+"px";this.separator.style.height=d+"px"}if(mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,true)){e=Math.min(e,this.bounds.height);this.label.style.height=e+"px";this.updateVmlShape(this.content);this.content.style.background="";this.content.style.top=e+"px";this.content.style.left="0px";this.content.style.height=Math.max(0,d-e)+"px";if(this.separator!=null)this.separator.path=
-"m "+(c-a)+" "+(e-b)+" l "+(c-a)+" "+(d-b)+" e"}else{e=Math.min(e,this.bounds.width);this.label.style.width=e+"px";this.updateVmlShape(this.content);this.content.style.background="";this.content.style.top="0px";this.content.style.left=e+"px";this.content.style.width=Math.max(0,c-e)+"px";if(this.separator!=null)this.separator.path="m "+(e-a)+" "+(d-b)+" l "+(c-a)+" "+(d-b)+" e"}if(this.imageNode!=null){a=Math.round(this.imageSize*this.scale);this.imageNode.style.left=c-a-4+"px";this.imageNode.style.top=
-"0px";this.imageNode.style.width=a+"px";this.imageNode.style.height=a+"px"}this.content.style.rotation=null};
-mxSwimlane.prototype.createSvg=function(){var a=this.createSvgGroup("rect");if(this.isRounded){this.innerNode.setAttribute("rx",10);this.innerNode.setAttribute("ry",10)}this.content=document.createElementNS(mxConstants.NS_SVG,"path");this.configureSvgShape(this.content);this.content.setAttribute("fill","none");if(this.isRounded){this.content.setAttribute("rx",10);this.content.setAttribute("ry",10)}a.appendChild(this.content);var b=this.style[mxConstants.STYLE_SEPARATORCOLOR];if(b!=null){this.separator=
-document.createElementNS(mxConstants.NS_SVG,"line");this.separator.setAttribute("stroke",b);this.separator.setAttribute("fill","none");this.separator.setAttribute("stroke-dasharray","2, 2");a.appendChild(this.separator)}if(this.image!=null){this.imageNode=document.createElementNS(mxConstants.NS_SVG,"image");this.imageNode.setAttributeNS(mxConstants.NS_XLINK,"href",this.image);this.configureSvgShape(this.imageNode);a.appendChild(this.imageNode)}return a};
-mxSwimlane.prototype.redrawSvg=function(){var a=this.isRounded;this.isRounded=false;this.updateSvgShape(this.innerNode);this.updateSvgShape(this.content);var b=mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,true);this.startSize=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE));var c=this.startSize*this.scale;if(this.shadowNode!=null){this.updateSvgShape(this.shadowNode);b?this.shadowNode.setAttribute("height",c):this.shadowNode.setAttribute("width",
-c)}this.isRounded=a;this.content.removeAttribute("x");this.content.removeAttribute("y");this.content.removeAttribute("width");this.content.removeAttribute("height");var d=this.crisp&&mxClient.IS_IE?0.5:0,a=Math.round(this.bounds.x)+d,d=Math.round(this.bounds.y)+d,e=Math.round(this.bounds.width),f=Math.round(this.bounds.height);if(b){c=Math.min(c,f);this.innerNode.setAttribute("height",c);this.content.setAttribute("d","M "+a+" "+(d+c)+" l 0 "+(f-c)+" l "+e+" 0 l 0 "+(c-f));if(this.separator!=null){this.separator.setAttribute("x1",
-a+e);this.separator.setAttribute("y1",d+c);this.separator.setAttribute("x2",a+e);this.separator.setAttribute("y2",d+f)}}else{c=Math.min(c,e);this.innerNode.setAttribute("width",c);this.content.setAttribute("d","M "+(a+c)+" "+d+" l "+(e-c)+" 0 l 0 "+f+" l "+(c-e)+" 0");if(this.separator!=null){this.separator.setAttribute("x1",a+c);this.separator.setAttribute("y1",d+f);this.separator.setAttribute("x2",a+e);this.separator.setAttribute("y2",d+f)}}if(this.imageNode!=null){this.imageNode.setAttribute("x",
-a+e-this.imageSize-4);this.imageNode.setAttribute("y",d);this.imageNode.setAttribute("width",this.imageSize*this.scale+"px");this.imageNode.setAttribute("height",this.imageSize*this.scale+"px")}};function mxGraphLayout(a){this.graph=a}mxGraphLayout.prototype.graph=null;mxGraphLayout.prototype.useBoundingBox=!0;mxGraphLayout.prototype.parent=null;mxGraphLayout.prototype.moveCell=function(){};mxGraphLayout.prototype.execute=function(){};mxGraphLayout.prototype.getGraph=function(){return this.graph};
-mxGraphLayout.prototype.getConstraint=function(a,b){var c=this.graph.view.getState(b),c=c!=null?c.style:this.graph.getCellStyle(b);return c!=null?c[a]:null};
-mxGraphLayout.traverse=function(a,b,c,d,e){if(c!=null&&a!=null){var b=b!=null?b:true,e=e||[],f=mxCellPath.create(a);if(e[f]==null){e[f]=a;d=c(a,d);if(d==null||d){d=this.graph.model.getEdgeCount(a);if(d>0)for(f=0;f<d;f++){var g=this.graph.model.getEdgeAt(a,f),h=this.graph.model.getTerminal(g,true)==a;(!b||h)&&this.traverse(this.graph.view.getVisibleTerminal(g,!h),b,c,g,e)}}}}};mxGraphLayout.prototype.isVertexMovable=function(a){return this.graph.isCellMovable(a)};
-mxGraphLayout.prototype.isVertexIgnored=function(a){return!this.graph.getModel().isVertex(a)||!this.graph.isCellVisible(a)};mxGraphLayout.prototype.isEdgeIgnored=function(a){var b=this.graph.getModel();return!b.isEdge(a)||!this.graph.isCellVisible(a)||b.getTerminal(a,true)==null||b.getTerminal(a,false)==null};mxGraphLayout.prototype.setEdgeStyleEnabled=function(a,b){this.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE,b?"0":"1",[a])};
-mxGraphLayout.prototype.setOrthogonalEdge=function(a,b){this.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL,b?"1":"0",[a])};mxGraphLayout.prototype.getParentOffset=function(a){var b=new mxPoint;if(a!=null&&a!=this.parent){var c=this.graph.getModel();if(c.isAncestor(this.parent,a))for(var d=c.getGeometry(a);a!=this.parent;){b.x=b.x+d.x;b.y=b.y+d.y;a=c.getParent(a);d=c.getGeometry(a)}}return b};
-mxGraphLayout.prototype.setEdgePoints=function(a,b){if(a!=null){var c=this.graph.model,d=c.getGeometry(a);if(d==null){d=new mxGeometry;d.setRelative(true)}else d=d.clone();if(this.parent!=null&&b!=null)for(var e=this.getParentOffset(c.getParent(a)),f=0;f<b.length;f++){b[f].x=b[f].x-e.x;b[f].y=b[f].y-e.y}d.points=b;c.setGeometry(a,d)}};
-mxGraphLayout.prototype.setVertexLocation=function(a,b,c){var d=this.graph.getModel(),e=d.getGeometry(a),f=null;if(e!=null){f=new mxRectangle(b,c,e.width,e.height);if(this.useBoundingBox){var g=this.graph.getView().getState(a);if(g!=null&&g.text!=null&&g.text.boundingBox!=null){var h=this.graph.getView().scale,k=g.text.boundingBox;if(g.text.boundingBox.x<g.x){b=b+(g.x-k.x)/h;f.width=k.width}if(g.text.boundingBox.y<g.y){c=c+(g.y-k.y)/h;f.height=k.height}}}if(this.parent!=null){g=d.getParent(a);if(g!=
-null&&g!=this.parent){g=this.getParentOffset(g);b=b-g.x;c=c-g.y}}if(e.x!=b||e.y!=c){e=e.clone();e.x=b;e.y=c;d.setGeometry(a,e)}}return f};
-mxGraphLayout.prototype.getVertexBounds=function(a){var b=this.graph.getModel().getGeometry(a);if(this.useBoundingBox){var c=this.graph.getView().getState(a);if(c!=null&&c.text!=null&&c.text.boundingBox!=null)var d=this.graph.getView().scale,e=c.text.boundingBox,f=Math.max(c.x-e.x,0)/d,g=Math.max(c.y-e.y,0)/d,h=Math.max(e.x+e.width-(c.x+c.width),0)/d,c=Math.max(e.y+e.height-(c.y+c.height),0)/d,b=new mxRectangle(b.x-f,b.y-g,b.width+f+h,b.height+g+c)}if(this.parent!=null){a=this.graph.getModel().getParent(a);
-b=b.clone();if(a!=null&&a!=this.parent){a=this.getParentOffset(a);b.x=b.x+a.x;b.y=b.y+a.y}}return new mxRectangle(b.x,b.y,b.width,b.height)};
-mxGraphLayout.prototype.arrangeGroups=function(a,b){this.graph.getModel().beginUpdate();try{for(var c=a.length-1;c>=0;c--){var d=a[c],e=this.graph.getChildVertices(d),f=this.graph.getBoundingBoxFromGeometry(e),g=this.graph.getCellGeometry(d),h=0,k=0;if(this.graph.isSwimlane(d))var i=this.graph.getStartSize(d),h=i.width,k=i.height;if(f!=null&&g!=null){g=g.clone();g.x=g.x+f.x-b-h;g.y=g.y+f.y-b-k;g.width=f.width+2*b+h;g.height=f.height+2*b+k;this.graph.getModel().setGeometry(d,g);this.graph.moveCells(e,
-b+h-f.x,b+k-f.y)}}}finally{this.graph.getModel().endUpdate()}};function mxStackLayout(a,b,c,d,e,f){mxGraphLayout.call(this,a);this.horizontal=b!=null?b:true;this.spacing=c!=null?c:0;this.x0=d!=null?d:0;this.y0=e!=null?e:0;this.border=f!=null?f:0}mxStackLayout.prototype=new mxGraphLayout;mxStackLayout.prototype.constructor=mxStackLayout;mxStackLayout.prototype.horizontal=null;mxStackLayout.prototype.spacing=null;mxStackLayout.prototype.x0=null;mxStackLayout.prototype.y0=null;
-mxStackLayout.prototype.border=0;mxStackLayout.prototype.keepFirstLocation=!1;mxStackLayout.prototype.fill=!1;mxStackLayout.prototype.resizeParent=!1;mxStackLayout.prototype.resizeLast=!1;mxStackLayout.prototype.wrap=null;mxStackLayout.prototype.isHorizontal=function(){return this.horizontal};
-mxStackLayout.prototype.moveCell=function(a,b,c){var d=this.graph.getModel(),e=d.getParent(a),f=this.isHorizontal();if(a!=null&&e!=null){var g=0,h=0,k=d.getChildCount(e),b=f?b:c,g=this.graph.getView().getState(e);g!=null&&(b=b-(f?g.x:g.y));for(g=0;g<k;g++){c=d.getChildAt(e,g);if(c!=a){c=d.getGeometry(c);if(c!=null){c=f?c.x+c.width/2:c.y+c.height/2;if(h<b&&c>b)break;h=c}}}f=e.getIndex(a);f=Math.max(0,g-(g>f?1:0));d.add(e,a,f)}};
-mxStackLayout.prototype.getParentSize=function(a){var b=this.graph.getModel(),c=b.getGeometry(a);if(this.graph.container!=null&&(c==null&&b.isLayer(a)||a==this.graph.getView().currentRoot))c=new mxRectangle(0,0,this.graph.container.offsetWidth-1,this.graph.container.offsetHeight-1);return c};
-mxStackLayout.prototype.execute=function(a){if(a!=null){var b=this.isHorizontal(),c=this.graph.getModel(),d=this.getParentSize(a),e=0;d!=null&&(e=b?d.height:d.width);var e=e-(2*this.spacing+2*this.border),f=this.x0+this.border,g=this.y0+this.border;if(this.graph.isSwimlane(a)){var h=this.graph.getCellStyle(a),k=mxUtils.getValue(h,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE),h=mxUtils.getValue(h,mxConstants.STYLE_HORIZONTAL,true);b==h&&(e=e-k);b?g=g+k:f=f+k}c.beginUpdate();try{for(var k=
-0,h=null,i=c.getChildCount(a),l=0;l<i;l++){var m=c.getChildAt(a,l);if(!this.isVertexIgnored(m)&&this.isVertexMovable(m)){var n=c.getGeometry(m);if(n!=null){n=n.clone();if(this.wrap!=null&&h!=null&&(b&&h.x+h.width+n.width+2*this.spacing>this.wrap||!b&&h.y+h.height+n.height+2*this.spacing>this.wrap)){h=null;b?g=g+(k+this.spacing):f=f+(k+this.spacing);k=0}k=Math.max(k,b?n.height:n.width);if(h!=null)b?n.x=h.x+h.width+this.spacing:n.y=h.y+h.height+this.spacing;else if(!this.keepFirstLocation)b?n.x=f:n.y=
-g;b?n.y=g:n.x=f;if(this.fill&&e>0)b?n.height=e:n.width=e;c.setGeometry(m,n);h=n}}}if(this.resizeParent&&d!=null&&h!=null&&!this.graph.isCellCollapsed(a)){d=d.clone();b?d.width=h.x+h.width+this.spacing:d.height=h.y+h.height+this.spacing;c.setGeometry(a,d)}else if(this.resizeLast&&d!=null&&h!=null)b?h.width=d.width-h.x-this.spacing:h.height=d.height-h.y-this.spacing}finally{c.endUpdate()}}};
-function mxPartitionLayout(a,b,c,d){mxGraphLayout.call(this,a);this.horizontal=b!=null?b:true;this.spacing=c||0;this.border=d||0}mxPartitionLayout.prototype=new mxGraphLayout;mxPartitionLayout.prototype.constructor=mxPartitionLayout;mxPartitionLayout.prototype.horizontal=null;mxPartitionLayout.prototype.spacing=null;mxPartitionLayout.prototype.border=null;mxPartitionLayout.prototype.resizeVertices=!0;mxPartitionLayout.prototype.isHorizontal=function(){return this.horizontal};
-mxPartitionLayout.prototype.moveCell=function(a,b){var c=this.graph.getModel(),d=c.getParent(a);if(a!=null&&d!=null){for(var e=0,f=0,g=c.getChildCount(d),e=0;e<g;e++){var h=this.getVertexBounds(c.getChildAt(d,e));if(h!=null){h=h.x+h.width/2;if(f<b&&h>b)break;f=h}}f=d.getIndex(a);f=Math.max(0,e-(e>f?1:0));c.add(d,a,f)}};
-mxPartitionLayout.prototype.execute=function(a){var b=this.isHorizontal(),c=this.graph.getModel(),d=c.getGeometry(a);if(this.graph.container!=null&&(d==null&&c.isLayer(a)||a==this.graph.getView().currentRoot))d=new mxRectangle(0,0,this.graph.container.offsetWidth-1,this.graph.container.offsetHeight-1);if(d!=null){for(var e=[],f=c.getChildCount(a),g=0;g<f;g++){var h=c.getChildAt(a,g);!this.isVertexIgnored(h)&&this.isVertexMovable(h)&&e.push(h)}f=e.length;if(f>0){var k=this.border,i=this.border,l=b?
-d.height:d.width,l=l-2*this.border,a=this.graph.isSwimlane(a)?this.graph.getStartSize(a):new mxRectangle,l=l-(b?a.height:a.width),k=k+a.width,i=i+a.height,a=this.border+(f-1)*this.spacing,d=b?(d.width-k-a)/f:(d.height-i-a)/f;if(d>0){c.beginUpdate();try{for(g=0;g<f;g++){var h=e[g],m=c.getGeometry(h);if(m!=null){m=m.clone();m.x=k;m.y=i;if(b){if(this.resizeVertices){m.width=d;m.height=l}k=k+(d+this.spacing)}else{if(this.resizeVertices){m.height=d;m.width=l}i=i+(d+this.spacing)}c.setGeometry(h,m)}}}finally{c.endUpdate()}}}}};
-function mxCompactTreeLayout(a,b,c){mxGraphLayout.call(this,a);this.horizontal=b!=null?b:true;this.invert=c!=null?c:false}mxCompactTreeLayout.prototype=new mxGraphLayout;mxCompactTreeLayout.prototype.constructor=mxCompactTreeLayout;mxCompactTreeLayout.prototype.horizontal=null;mxCompactTreeLayout.prototype.invert=null;mxCompactTreeLayout.prototype.resizeParent=!0;mxCompactTreeLayout.prototype.groupPadding=10;mxCompactTreeLayout.prototype.parentsChanged=null;
-mxCompactTreeLayout.prototype.moveTree=!1;mxCompactTreeLayout.prototype.levelDistance=10;mxCompactTreeLayout.prototype.nodeDistance=20;mxCompactTreeLayout.prototype.resetEdges=!0;mxCompactTreeLayout.prototype.prefHozEdgeSep=5;mxCompactTreeLayout.prototype.prefVertEdgeOff=4;mxCompactTreeLayout.prototype.minEdgeJetty=8;mxCompactTreeLayout.prototype.channelBuffer=4;mxCompactTreeLayout.prototype.edgeRouting=!0;
-mxCompactTreeLayout.prototype.isVertexIgnored=function(a){return mxGraphLayout.prototype.isVertexIgnored.apply(this,arguments)||this.graph.getConnections(a).length==0};mxCompactTreeLayout.prototype.isHorizontal=function(){return this.horizontal};
-mxCompactTreeLayout.prototype.execute=function(a,b){this.parent=a;var c=this.graph.getModel();if(b==null)if(this.graph.getEdges(a,c.getParent(a),this.invert,!this.invert,false).length>0)b=a;else{var d=this.graph.findTreeRoots(a,true,this.invert);if(d.length>0)for(var e=0;e<d.length;e++)if(!this.isVertexIgnored(d[e])&&this.graph.getEdges(d[e],null,this.invert,!this.invert,false).length>0){b=d[e];break}}if(b!=null){this.parentsChanged=this.resizeParent?{}:null;c.beginUpdate();try{var f=this.dfs(b,a);
-if(f!=null){this.layout(f);var g=this.graph.gridSize,d=g;if(!this.moveTree){var h=this.getVertexBounds(b);if(h!=null){g=h.x;d=h.y}}h=null;h=this.isHorizontal()?this.horizontalLayout(f,g,d):this.verticalLayout(f,null,g,d);if(h!=null){var k=e=0;h.x<0&&(e=Math.abs(g-h.x));h.y<0&&(k=Math.abs(d-h.y));(e!=0||k!=0)&&this.moveNode(f,e,k);this.resizeParent&&this.adjustParents();this.edgeRouting&&this.localEdgeProcessing(f)}}}finally{c.endUpdate()}}};
-mxCompactTreeLayout.prototype.moveNode=function(a,b,c){a.x=a.x+b;a.y=a.y+c;this.apply(a);for(a=a.child;a!=null;){this.moveNode(a,b,c);a=a.next}};
-mxCompactTreeLayout.prototype.dfs=function(a,b,c){var c=c!=null?c:[],d=mxCellPath.create(a),e=null;if(a!=null&&c[d]==null&&!this.isVertexIgnored(a)){c[d]=a;for(var e=this.createNode(a),d=this.graph.getModel(),f=null,a=this.graph.getEdges(a,b,this.invert,!this.invert,false,true),g=this.graph.getView(),h=0;h<a.length;h++){var k=a[h];if(!this.isEdgeIgnored(k)){this.resetEdges&&this.setEdgePoints(k,null);if(this.edgeRouting){this.setEdgeStyleEnabled(k,false);this.setEdgePoints(k,null)}var i=g.getState(k),
-k=i!=null?i.getVisibleTerminal(this.invert):g.getVisibleTerminal(k,this.invert),i=this.dfs(k,b,c);if(i!=null&&d.getGeometry(k)!=null){f==null?e.child=i:f.next=i;f=i}}}}return e};mxCompactTreeLayout.prototype.layout=function(a){if(a!=null){for(var b=a.child;b!=null;){this.layout(b);b=b.next}a.child!=null?this.attachParent(a,this.join(a)):this.layoutLeaf(a)}};
-mxCompactTreeLayout.prototype.horizontalLayout=function(a,b,c,d){a.x=a.x+(b+a.offsetX);a.y=a.y+(c+a.offsetY);d=this.apply(a,d);b=a.child;if(b!=null)for(var d=this.horizontalLayout(b,a.x,a.y,d),c=a.y+b.offsetY,e=b.next;e!=null;){d=this.horizontalLayout(e,a.x+b.offsetX,c,d);c=c+e.offsetY;e=e.next}return d};
-mxCompactTreeLayout.prototype.verticalLayout=function(a,b,c,d,e){a.x=a.x+(c+a.offsetY);a.y=a.y+(d+a.offsetX);e=this.apply(a,e);b=a.child;if(b!=null){e=this.verticalLayout(b,a,a.x,a.y,e);c=a.x+b.offsetY;for(d=b.next;d!=null;){e=this.verticalLayout(d,a,c,a.y+b.offsetX,e);c=c+d.offsetY;d=d.next}}return e};
-mxCompactTreeLayout.prototype.attachParent=function(a,b){var c=this.nodeDistance+this.levelDistance,d=(b-a.width)/2-this.nodeDistance,e=d+a.width+2*this.nodeDistance-b;a.child.offsetX=c+a.height;a.child.offsetY=e;a.contour.upperHead=this.createLine(a.height,0,this.createLine(c,e,a.contour.upperHead));a.contour.lowerHead=this.createLine(a.height,0,this.createLine(c,d,a.contour.lowerHead))};
-mxCompactTreeLayout.prototype.layoutLeaf=function(a){var b=2*this.nodeDistance;a.contour.upperTail=this.createLine(a.height+b,0);a.contour.upperHead=a.contour.upperTail;a.contour.lowerTail=this.createLine(0,-a.width-b);a.contour.lowerHead=this.createLine(a.height+b,0,a.contour.lowerTail)};
-mxCompactTreeLayout.prototype.join=function(a){var b=2*this.nodeDistance,c=a.child;a.contour=c.contour;for(var d=c.width+b,e=d,c=c.next;c!=null;){var f=this.merge(a.contour,c.contour);c.offsetY=f+d;c.offsetX=0;d=c.width+b;e=e+(f+d);c=c.next}return e};
-mxCompactTreeLayout.prototype.merge=function(a,b){for(var c=0,d=0,e=0,f=a.lowerHead,g=b.upperHead;g!=null&&f!=null;){var h=this.offset(c,d,g.dx,g.dy,f.dx,f.dy),d=d+h,e=e+h;if(c+g.dx<=f.dx){c=c+g.dx;d=d+g.dy;g=g.next}else{c=c-f.dx;d=d-f.dy;f=f.next}}if(g!=null){c=this.bridge(a.upperTail,0,0,g,c,d);a.upperTail=c.next!=null?b.upperTail:c;a.lowerTail=b.lowerTail}else{c=this.bridge(b.lowerTail,c,d,f,0,0);if(c.next==null)a.lowerTail=c}a.lowerHead=b.lowerHead;return e};
-mxCompactTreeLayout.prototype.offset=function(a,b,c,d,e,f){var g=0;if(e<=a||a+c<=0)return 0;g=e*d-c*f>0?a<0?a*d/c-b:a>0?a*f/e-b:-b:e<a+c?f-(b+(e-a)*d/c):e>a+c?(c+a)*f/e-(b+d):f-(b+d);return g>0?g:0};mxCompactTreeLayout.prototype.bridge=function(a,b,c,d,e,f){b=e+d.dx-b;e=e=0;if(d.dx==0)e=d.dy;else{e=b*d.dy;e=e/d.dx}b=this.createLine(b,e,d.next);a.next=this.createLine(0,f+d.dy-e-c,b);return b};
-mxCompactTreeLayout.prototype.createNode=function(a){var b={};b.cell=a;b.x=0;b.y=0;b.width=0;b.height=0;a=this.getVertexBounds(a);if(a!=null)if(this.isHorizontal()){b.width=a.height;b.height=a.width}else{b.width=a.width;b.height=a.height}b.offsetX=0;b.offsetY=0;b.contour={};return b};
-mxCompactTreeLayout.prototype.apply=function(a,b){var c=this.graph.getModel(),d=a.cell,e=c.getGeometry(d);if(d!=null&&e!=null){if(this.isVertexMovable(d)){e=this.setVertexLocation(d,a.x,a.y);if(this.resizeParent){c=c.getParent(d);d=mxCellPath.create(c);this.parentsChanged[d]==null&&(this.parentsChanged[d]=c)}}b=b==null?new mxRectangle(e.x,e.y,e.width,e.height):new mxRectangle(Math.min(b.x,e.x),Math.min(b.y,e.y),Math.max(b.x+b.width,e.x+e.width),Math.max(b.y+b.height,e.y+e.height))}return b};
-mxCompactTreeLayout.prototype.createLine=function(a,b,c){var d={};d.dx=a;d.dy=b;d.next=c;return d};mxCompactTreeLayout.prototype.adjustParents=function(){var a=[],b;for(b in this.parentsChanged)a.push(this.parentsChanged[b]);this.arrangeGroups(mxUtils.sortCells(a,true),this.groupPadding)};mxCompactTreeLayout.prototype.localEdgeProcessing=function(a){this.processNodeOutgoing(a);for(a=a.child;a!=null;){this.localEdgeProcessing(a);a=a.next}};
-mxCompactTreeLayout.prototype.processNodeOutgoing=function(a){for(var b=a.child,c=a.cell,d=0,e=[];b!=null;){d++;var f=b.x;if(this.horizontal)f=b.y;e.push(new WeightedCellSorter(b,f));b=b.next}e.sort(WeightedCellSorter.prototype.compare);var f=a.width,g=(d+1)*this.prefHozEdgeSep;f>g+2*this.prefHozEdgeSep&&(f=f-2*this.prefHozEdgeSep);a=f/d;b=a/2;f>g+2*this.prefHozEdgeSep&&(b=b+this.prefHozEdgeSep);for(var f=this.minEdgeJetty-this.prefVertEdgeOff,g=0,h=this.getVertexBounds(c),k=0;k<e.length;k++){for(var i=
-e[k].cell.cell,l=this.getVertexBounds(i),i=this.graph.getEdgesBetween(c,i,false),m=[],n=0,o=0,p=0;p<i.length;p++){if(this.horizontal){n=h.x+h.width;o=h.y+b;m.push(new mxPoint(n,o));n=h.x+h.width+f;m.push(new mxPoint(n,o));o=l.y+l.height/2}else{n=h.x+b;o=h.y+h.height;m.push(new mxPoint(n,o));o=h.y+h.height+f;m.push(new mxPoint(n,o));n=l.x+l.width/2}m.push(new mxPoint(n,o));this.setEdgePoints(i[p],m)}k<d/2?f=f+this.prefVertEdgeOff:k>d/2&&(f=f-this.prefVertEdgeOff);b=b+a;g=Math.max(g,f)}};
-function WeightedCellSorter(a,b){this.cell=a;this.weightedValue=b}WeightedCellSorter.prototype.weightedValue=0;WeightedCellSorter.prototype.nudge=!1;WeightedCellSorter.prototype.visited=!1;WeightedCellSorter.prototype.rankIndex=null;WeightedCellSorter.prototype.cell=null;WeightedCellSorter.prototype.compare=function(a,b){return a!=null&&b!=null?b.weightedValue>a.weightedValue?1:b.weightedValue<a.weightedValue?-1:b.nudge?1:-1:0};function mxFastOrganicLayout(a){mxGraphLayout.call(this,a)}
-mxFastOrganicLayout.prototype=new mxGraphLayout;mxFastOrganicLayout.prototype.constructor=mxFastOrganicLayout;mxFastOrganicLayout.prototype.useInputOrigin=!0;mxFastOrganicLayout.prototype.resetEdges=!0;mxFastOrganicLayout.prototype.disableEdgeStyle=!0;mxFastOrganicLayout.prototype.forceConstant=50;mxFastOrganicLayout.prototype.forceConstantSquared=0;mxFastOrganicLayout.prototype.minDistanceLimit=2;mxFastOrganicLayout.prototype.maxDistanceLimit=500;
-mxFastOrganicLayout.prototype.minDistanceLimitSquared=4;mxFastOrganicLayout.prototype.initialTemp=200;mxFastOrganicLayout.prototype.temperature=0;mxFastOrganicLayout.prototype.maxIterations=0;mxFastOrganicLayout.prototype.iteration=0;mxFastOrganicLayout.prototype.allowedToRun=!0;mxFastOrganicLayout.prototype.isVertexIgnored=function(a){return mxGraphLayout.prototype.isVertexIgnored.apply(this,arguments)||this.graph.getConnections(a).length==0};
-mxFastOrganicLayout.prototype.execute=function(a){var b=this.graph.getModel();this.vertexArray=[];for(var c=this.graph.getChildVertices(a),d=0;d<c.length;d++)this.isVertexIgnored(c[d])||this.vertexArray.push(c[d]);var e=this.useInputOrigin?this.graph.view.getBounds(this.vertexArray):null,f=this.vertexArray.length;this.indices=[];this.dispX=[];this.dispY=[];this.cellLocation=[];this.isMoveable=[];this.neighbours=[];this.radius=[];this.radiusSquared=[];if(this.forceConstant<0.0010)this.forceConstant=
-0.0010;this.forceConstantSquared=this.forceConstant*this.forceConstant;for(d=0;d<this.vertexArray.length;d++){var g=this.vertexArray[d];this.cellLocation[d]=[];var h=mxCellPath.create(g);this.indices[h]=d;var k=this.getVertexBounds(g),i=k.width,l=k.height,m=k.x,n=k.y;this.cellLocation[d][0]=m+i/2;this.cellLocation[d][1]=n+l/2;this.radius[d]=Math.min(i,l);this.radiusSquared[d]=this.radius[d]*this.radius[d]}b.beginUpdate();try{for(d=0;d<f;d++){this.dispX[d]=0;this.dispY[d]=0;this.isMoveable[d]=this.isVertexMovable(this.vertexArray[d]);
-var o=this.graph.getConnections(this.vertexArray[d],a),c=this.graph.getOpposites(o,this.vertexArray[d]);this.neighbours[d]=[];for(i=0;i<c.length;i++){this.resetEdges&&this.graph.resetEdge(o[i]);this.disableEdgeStyle&&this.setEdgeStyleEnabled(o[i],false);var h=mxCellPath.create(c[i]),p=this.indices[h];this.neighbours[d][i]=p!=null?p:d}}this.temperature=this.initialTemp;if(this.maxIterations==0)this.maxIterations=20*Math.sqrt(f);for(this.iteration=0;this.iteration<this.maxIterations;this.iteration++){if(!this.allowedToRun)return;
-this.calcRepulsion();this.calcAttraction();this.calcPositions();this.reduceTemperature()}a=c=null;for(d=0;d<this.vertexArray.length;d++){g=this.vertexArray[d];if(this.isVertexMovable(g)){k=this.getVertexBounds(g);if(k!=null){this.cellLocation[d][0]=this.cellLocation[d][0]-k.width/2;this.cellLocation[d][1]=this.cellLocation[d][1]-k.height/2;m=this.graph.snap(this.cellLocation[d][0]);n=this.graph.snap(this.cellLocation[d][1]);this.setVertexLocation(g,m,n);c=c==null?m:Math.min(c,m);a=a==null?n:Math.min(a,
-n)}}}d=-(c||0)+1;g=-(a||0)+1;if(e!=null){d=d+e.x;g=g+e.y}this.graph.moveCells(this.vertexArray,d,g)}finally{b.endUpdate()}};
-mxFastOrganicLayout.prototype.calcPositions=function(){for(var a=0;a<this.vertexArray.length;a++)if(this.isMoveable[a]){var b=Math.sqrt(this.dispX[a]*this.dispX[a]+this.dispY[a]*this.dispY[a]);b<0.0010&&(b=0.0010);var c=this.dispX[a]/b*Math.min(b,this.temperature),b=this.dispY[a]/b*Math.min(b,this.temperature);this.dispX[a]=0;this.dispY[a]=0;this.cellLocation[a][0]=this.cellLocation[a][0]+c;this.cellLocation[a][1]=this.cellLocation[a][1]+b}};
-mxFastOrganicLayout.prototype.calcAttraction=function(){for(var a=0;a<this.vertexArray.length;a++)for(var b=0;b<this.neighbours[a].length;b++){var c=this.neighbours[a][b];if(a!=c&&this.isMoveable[a]&&this.isMoveable[c]){var d=this.cellLocation[a][0]-this.cellLocation[c][0],e=this.cellLocation[a][1]-this.cellLocation[c][1],f=d*d+e*e-this.radiusSquared[a]-this.radiusSquared[c];if(f<this.minDistanceLimitSquared)f=this.minDistanceLimitSquared;var g=Math.sqrt(f),f=f/this.forceConstant,d=d/g*f,e=e/g*f;
-this.dispX[a]=this.dispX[a]-d;this.dispY[a]=this.dispY[a]-e;this.dispX[c]=this.dispX[c]+d;this.dispY[c]=this.dispY[c]+e}}};
-mxFastOrganicLayout.prototype.calcRepulsion=function(){for(var a=this.vertexArray.length,b=0;b<a;b++)for(var c=b;c<a;c++){if(!this.allowedToRun)return;if(c!=b&&this.isMoveable[b]&&this.isMoveable[c]){var d=this.cellLocation[b][0]-this.cellLocation[c][0],e=this.cellLocation[b][1]-this.cellLocation[c][1];d==0&&(d=0.01+Math.random());e==0&&(e=0.01+Math.random());var f=Math.sqrt(d*d+e*e),g=f-this.radius[b]-this.radius[c];if(!(g>this.maxDistanceLimit)){if(g<this.minDistanceLimit)g=this.minDistanceLimit;
-g=this.forceConstantSquared/g;d=d/f*g;e=e/f*g;this.dispX[b]=this.dispX[b]+d;this.dispY[b]=this.dispY[b]+e;this.dispX[c]=this.dispX[c]-d;this.dispY[c]=this.dispY[c]-e}}}};mxFastOrganicLayout.prototype.reduceTemperature=function(){this.temperature=this.initialTemp*(1-this.iteration/this.maxIterations)};function mxCircleLayout(a,b){mxGraphLayout.call(this,a);this.radius=b!=null?b:100}mxCircleLayout.prototype=new mxGraphLayout;mxCircleLayout.prototype.constructor=mxCircleLayout;
-mxCircleLayout.prototype.radius=null;mxCircleLayout.prototype.moveCircle=!1;mxCircleLayout.prototype.x0=0;mxCircleLayout.prototype.y0=0;mxCircleLayout.prototype.resetEdges=!0;mxCircleLayout.prototype.disableEdgeStyle=!0;
-mxCircleLayout.prototype.execute=function(a){var b=this.graph.getModel();b.beginUpdate();try{for(var c=0,d=null,e=null,f=[],g=b.getChildCount(a),h=0;h<g;h++){var k=b.getChildAt(a,h);if(this.isVertexIgnored(k)){if(!this.isEdgeIgnored(k)){this.resetEdges&&this.graph.resetEdge(k);this.disableEdgeStyle&&this.setEdgeStyleEnabled(k,false)}}else{f.push(k);var i=this.getVertexBounds(k),d=d==null?i.y:Math.min(d,i.y),e=e==null?i.x:Math.min(e,i.x),c=Math.max(c,Math.max(i.width,i.height))}}var l=this.getRadius(f.length,
-c);if(this.moveCircle){e=this.x0;d=this.y0}this.circle(f,l,e,d)}finally{b.endUpdate()}};mxCircleLayout.prototype.getRadius=function(a,b){return Math.max(a*b/Math.PI,this.radius)};mxCircleLayout.prototype.circle=function(a,b,c,d){for(var e=a.length,f=2*Math.PI/e,g=0;g<e;g++)this.isVertexMovable(a[g])&&this.setVertexLocation(a[g],c+b+b*Math.sin(g*f),d+b+b*Math.cos(g*f))};function mxParallelEdgeLayout(a){mxGraphLayout.call(this,a)}mxParallelEdgeLayout.prototype=new mxGraphLayout;
-mxParallelEdgeLayout.prototype.constructor=mxParallelEdgeLayout;mxParallelEdgeLayout.prototype.spacing=20;mxParallelEdgeLayout.prototype.execute=function(a){a=this.findParallels(a);this.graph.model.beginUpdate();try{for(var b in a){var c=a[b];c.length>1&&this.layout(c)}}finally{this.graph.model.endUpdate()}};
-mxParallelEdgeLayout.prototype.findParallels=function(a){for(var b=this.graph.getModel(),c=[],d=b.getChildCount(a),e=0;e<d;e++){var f=b.getChildAt(a,e);if(!this.isEdgeIgnored(f)){var g=this.getEdgeId(f);if(g!=null){c[g]==null&&(c[g]=[]);c[g].push(f)}}}return c};
-mxParallelEdgeLayout.prototype.getEdgeId=function(a){var b=this.graph.getView(),c=b.getState(a),d=c!=null?c.getVisibleTerminal(true):b.getVisibleTerminal(a,true),a=c!=null?c.getVisibleTerminal(false):b.getVisibleTerminal(a,false);if(d!=null&&a!=null){d=mxCellPath.create(d);a=mxCellPath.create(a);return d>a?a+"-"+d:d+"-"+a}return null};
-mxParallelEdgeLayout.prototype.layout=function(a){var b=a[0],c=this.graph.getModel(),d=c.getGeometry(c.getTerminal(b,true)),e=c.getGeometry(c.getTerminal(b,false));if(d==e)for(var b=d.x+d.width+this.spacing,c=d.y+d.height/2,f=0;f<a.length;f++){this.route(a[f],b,c);b=b+this.spacing}else if(d!=null&&e!=null)for(var b=d.x+d.width/2,c=d.y+d.height/2,f=e.x+e.width/2-b,g=e.y+e.height/2-c,e=Math.sqrt(f*f+g*g),d=g*this.spacing/e,e=f*this.spacing/e,b=b+f/2+d*(a.length-1)/2,c=c+g/2-e*(a.length-1)/2,f=0;f<a.length;f++){this.route(a[f],
-b,c);b=b-d;c=c+e}};mxParallelEdgeLayout.prototype.route=function(a,b,c){this.graph.isCellMovable(a)&&this.setEdgePoints(a,[new mxPoint(b,c)])};function mxCompositeLayout(a,b,c){mxGraphLayout.call(this,a);this.layouts=b;this.master=c}mxCompositeLayout.prototype=new mxGraphLayout;mxCompositeLayout.prototype.constructor=mxCompositeLayout;mxCompositeLayout.prototype.layouts=null;mxCompositeLayout.prototype.master=null;
-mxCompositeLayout.prototype.moveCell=function(a,b,c){this.master!=null?this.master.move.apply(this.master,arguments):this.layouts[0].move.apply(this.layouts[0],arguments)};mxCompositeLayout.prototype.execute=function(a){var b=this.graph.getModel();b.beginUpdate();try{for(var c=0;c<this.layouts.length;c++)this.layouts[c].execute.apply(this.layouts[c],arguments)}finally{b.endUpdate()}};function mxEdgeLabelLayout(a){mxGraphLayout.call(this,a)}mxEdgeLabelLayout.prototype=new mxGraphLayout;
-mxEdgeLabelLayout.prototype.constructor=mxEdgeLabelLayout;mxEdgeLabelLayout.prototype.execute=function(a){for(var b=this.graph.view,c=this.graph.getModel(),d=[],e=[],f=c.getChildCount(a),g=0;g<f;g++){var h=c.getChildAt(a,g),k=b.getState(h);k!=null&&(this.isVertexIgnored(h)?this.isEdgeIgnored(h)||d.push(k):e.push(k))}this.placeLabels(e,d)};
-mxEdgeLabelLayout.prototype.placeLabels=function(a,b){var c=this.graph.getModel();c.beginUpdate();try{for(var d=0;d<b.length;d++){var e=b[d];if(e!=null&&e.text!=null&&e.text.boundingBox!=null)for(var f=0;f<a.length;f++){var g=a[f];g!=null&&this.avoid(e,g)}}}finally{c.endUpdate()}};
-mxEdgeLabelLayout.prototype.avoid=function(a,b){var c=this.graph.getModel(),d=a.text.boundingBox;if(mxUtils.intersects(d,b)){var e=-d.y-d.height+b.y,f=-d.y+b.y+b.height,e=Math.abs(e)<Math.abs(f)?e:f,f=-d.x-d.width+b.x,d=-d.x+b.x+b.width,d=Math.abs(f)<Math.abs(d)?f:d;Math.abs(d)<Math.abs(e)?e=0:d=0;f=c.getGeometry(a.cell);if(f!=null){f=f.clone();if(f.offset!=null){f.offset.x=f.offset.x+d;f.offset.y=f.offset.y+e}else f.offset=new mxPoint(d,e);c.setGeometry(a.cell,f)}}};
-function mxGraphAbstractHierarchyCell(){this.x=[];this.y=[];this.temp=[]}mxGraphAbstractHierarchyCell.prototype.maxRank=-1;mxGraphAbstractHierarchyCell.prototype.minRank=-1;mxGraphAbstractHierarchyCell.prototype.x=null;mxGraphAbstractHierarchyCell.prototype.y=null;mxGraphAbstractHierarchyCell.prototype.width=0;mxGraphAbstractHierarchyCell.prototype.height=0;mxGraphAbstractHierarchyCell.prototype.nextLayerConnectedCells=null;mxGraphAbstractHierarchyCell.prototype.previousLayerConnectedCells=null;
-mxGraphAbstractHierarchyCell.prototype.temp=null;mxGraphAbstractHierarchyCell.prototype.getNextLayerConnectedCells=function(){return null};mxGraphAbstractHierarchyCell.prototype.getPreviousLayerConnectedCells=function(){return null};mxGraphAbstractHierarchyCell.prototype.isEdge=function(){return false};mxGraphAbstractHierarchyCell.prototype.isVertex=function(){return false};mxGraphAbstractHierarchyCell.prototype.getGeneralPurposeVariable=function(){return null};
-mxGraphAbstractHierarchyCell.prototype.setGeneralPurposeVariable=function(){return null};mxGraphAbstractHierarchyCell.prototype.setX=function(a,b){this.isVertex()?this.x[0]=b:this.isEdge()&&(this.x[a-this.minRank-1]=b)};mxGraphAbstractHierarchyCell.prototype.getX=function(a){return this.isVertex()?this.x[0]:this.isEdge()?this.x[a-this.minRank-1]:0};mxGraphAbstractHierarchyCell.prototype.setY=function(a,b){this.isVertex()?this.y[0]=b:this.isEdge()&&(this.y[a-this.minRank-1]=b)};
-function mxGraphHierarchyNode(a){mxGraphAbstractHierarchyCell.apply(this,arguments);this.cell=a}mxGraphHierarchyNode.prototype=new mxGraphAbstractHierarchyCell;mxGraphHierarchyNode.prototype.constructor=mxGraphHierarchyNode;mxGraphHierarchyNode.prototype.cell=null;mxGraphHierarchyNode.prototype.connectsAsTarget=[];mxGraphHierarchyNode.prototype.connectsAsSource=[];mxGraphHierarchyNode.prototype.hashCode=!1;mxGraphHierarchyNode.prototype.getRankValue=function(){return this.maxRank};
-mxGraphHierarchyNode.prototype.getNextLayerConnectedCells=function(a){if(this.nextLayerConnectedCells==null){this.nextLayerConnectedCells=[];this.nextLayerConnectedCells[0]=[];for(var b=0;b<this.connectsAsTarget.length;b++){var c=this.connectsAsTarget[b];c.maxRank==-1||c.maxRank==a+1?this.nextLayerConnectedCells[0].push(c.source):this.nextLayerConnectedCells[0].push(c)}}return this.nextLayerConnectedCells[0]};
-mxGraphHierarchyNode.prototype.getPreviousLayerConnectedCells=function(a){if(this.previousLayerConnectedCells==null){this.previousLayerConnectedCells=[];this.previousLayerConnectedCells[0]=[];for(var b=0;b<this.connectsAsSource.length;b++){var c=this.connectsAsSource[b];c.minRank==-1||c.minRank==a-1?this.previousLayerConnectedCells[0].push(c.target):this.previousLayerConnectedCells[0].push(c)}}return this.previousLayerConnectedCells[0]};mxGraphHierarchyNode.prototype.isVertex=function(){return true};
-mxGraphHierarchyNode.prototype.getGeneralPurposeVariable=function(){return this.temp[0]};mxGraphHierarchyNode.prototype.setGeneralPurposeVariable=function(a,b){this.temp[0]=b};
-mxGraphHierarchyNode.prototype.isAncestor=function(a){if(a!=null&&this.hashCode!=null&&a.hashCode!=null&&this.hashCode.length<a.hashCode.length){if(this.hashCode==a.hashCode)return true;if(this.hashCode==null||this.hashCode==null)return false;for(var b=0;b<this.hashCode.length;b++)if(this.hashCode[b]!=a.hashCode[b])return false;return true}return false};mxGraphHierarchyNode.prototype.getCoreCell=function(){return this.cell};
-function mxGraphHierarchyEdge(a){mxGraphAbstractHierarchyCell.apply(this,arguments);this.edges=a}mxGraphHierarchyEdge.prototype=new mxGraphAbstractHierarchyCell;mxGraphHierarchyEdge.prototype.constructor=mxGraphHierarchyEdge;mxGraphHierarchyEdge.prototype.edges=null;mxGraphHierarchyEdge.prototype.source=null;mxGraphHierarchyEdge.prototype.target=null;mxGraphHierarchyEdge.prototype.isReversed=!1;
-mxGraphHierarchyEdge.prototype.invert=function(){var a=this.source;this.source=this.target;this.target=a;this.isReversed=!this.isReversed};
-mxGraphHierarchyEdge.prototype.getNextLayerConnectedCells=function(a){if(this.nextLayerConnectedCells==null){this.nextLayerConnectedCells=[];for(var b=0;b<this.temp.length;b++){this.nextLayerConnectedCells[b]=[];b==this.temp.length-1?this.nextLayerConnectedCells[b].push(this.source):this.nextLayerConnectedCells[b].push(this)}}return this.nextLayerConnectedCells[a-this.minRank-1]};
-mxGraphHierarchyEdge.prototype.getPreviousLayerConnectedCells=function(a){if(this.previousLayerConnectedCells==null){this.previousLayerConnectedCells=[];for(var b=0;b<this.temp.length;b++){this.previousLayerConnectedCells[b]=[];b==0?this.previousLayerConnectedCells[b].push(this.target):this.previousLayerConnectedCells[b].push(this)}}return this.previousLayerConnectedCells[a-this.minRank-1]};mxGraphHierarchyEdge.prototype.isEdge=function(){return true};
-mxGraphHierarchyEdge.prototype.getGeneralPurposeVariable=function(a){return this.temp[a-this.minRank-1]};mxGraphHierarchyEdge.prototype.setGeneralPurposeVariable=function(a,b){this.temp[a-this.minRank-1]=b};mxGraphHierarchyEdge.prototype.getCoreCell=function(){return this.edges!=null&&this.edges.length>0?this.edges[0]:null};
-function mxGraphHierarchyModel(a,b,c,d,e){var f=a.getGraph();this.tightenToSource=e;this.roots=c;this.parent=d;this.vertexMapper={};this.edgeMapper={};this.maxRank=0;c=[];b==null&&(b=this.graph.getChildVertices(d));this.maxRank=this.SOURCESCANSTARTRANK;this.createInternalCells(a,b,c);for(a=0;a<b.length;a++){d=c[a].connectsAsSource;for(e=0;e<d.length;e++){var g=d[e],h=g.edges;if(h!=null&&h.length>0){var h=h[0],k=f.getView().getVisibleTerminal(h,false),k=mxCellPath.create(k),k=this.vertexMapper[k];
-if(c[a]==k){k=f.getView().getVisibleTerminal(h,true);k=mxCellPath.create(k);k=this.vertexMapper[k]}if(k!=null&&c[a]!=k){g.target=k;if(k.connectsAsTarget.length==0)k.connectsAsTarget=[];mxUtils.indexOf(k.connectsAsTarget,g)<0&&k.connectsAsTarget.push(g)}}}c[a].temp[0]=1}}mxGraphHierarchyModel.prototype.maxRank=null;mxGraphHierarchyModel.prototype.vertexMapper=null;mxGraphHierarchyModel.prototype.edgeMapper=null;mxGraphHierarchyModel.prototype.ranks=null;mxGraphHierarchyModel.prototype.roots=null;
-mxGraphHierarchyModel.prototype.parent=null;mxGraphHierarchyModel.prototype.dfsCount=0;mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK=1E8;mxGraphHierarchyModel.prototype.tightenToSource=!1;
-mxGraphHierarchyModel.prototype.createInternalCells=function(a,b,c){for(var d=a.getGraph(),e=0;e<b.length;e++){c[e]=new mxGraphHierarchyNode(b[e]);this.vertexMapper[mxCellPath.create(b[e])]=c[e];var f=a.getEdges(b[e]),f=d.getOpposites(f,b[e]);c[e].connectsAsSource=[];for(var g=0;g<f.length;g++){var h=f[g];if(h!=b[e]&&a.graph.model.isVertex(h)&&!a.isVertexIgnored(h)){var k=d.getEdgesBetween(b[e],h,false),i=d.getEdgesBetween(b[e],h,true),h=mxCellPath.create(k[0]);if(k!=null&&k.length>0&&this.edgeMapper[h]==
-null&&i.length*2>=k.length){for(var i=new mxGraphHierarchyEdge(k),l=0;l<k.length;l++){var m=k[l],h=mxCellPath.create(m);this.edgeMapper[h]=i;d.resetEdge(m);if(a.disableEdgeStyle){a.setEdgeStyleEnabled(m,false);a.setOrthogonalEdge(m,true)}}i.source=c[e];mxUtils.indexOf(c[e].connectsAsSource,i)<0&&c[e].connectsAsSource.push(i)}}}c[e].temp[0]=0}};
-mxGraphHierarchyModel.prototype.initialRank=function(){var a=[];if(this.roots!=null)for(var b=0;b<this.roots.length;b++){var c=this.vertexMapper[mxCellPath.create(this.roots[b])];c!=null&&a.push(c)}for(var d in this.vertexMapper){c=this.vertexMapper[d];c.temp[0]=-1}for(var e=a.slice();a.length>0;){var c=a[0],f,g;f=c.connectsAsTarget;g=c.connectsAsSource;for(var h=true,k=this.SOURCESCANSTARTRANK,b=0;b<f.length;b++){var i=f[b];if(i.temp[0]==5270620){i=i.source;k=Math.min(k,i.temp[0]-1)}else{h=false;
-break}}if(h){c.temp[0]=k;this.maxRank=Math.min(this.maxRank,k);if(g!=null)for(b=0;b<g.length;b++){i=g[b];i.temp[0]=5270620;i=i.target;if(i.temp[0]==-1){a.push(i);i.temp[0]=-2}}a.shift()}else{b=a.shift();a.push(c);if(b==c&&a.length==1)break}}for(d in this.vertexMapper){c=this.vertexMapper[d];c.temp[0]=c.temp[0]-this.maxRank}for(b=0;b<e.length;b++){c=e[b];a=0;f=c.connectsAsSource;for(d=0;d<f.length;d++){i=f[d];i=i.target;c.temp[0]=Math.max(a,i.temp[0]+1);a=c.temp[0]}}this.maxRank=this.SOURCESCANSTARTRANK-
-this.maxRank};
-mxGraphHierarchyModel.prototype.fixRanks=function(){var a=[];this.ranks=[];for(var b=0;b<this.maxRank+1;b++){a[b]=[];this.ranks[b]=a[b]}var c=null;if(this.roots!=null)for(var d=this.roots,c=[],b=0;b<d.length;b++){var e=this.vertexMapper[mxCellPath.create(d[b])];c[b]=e}this.visit(function(b,c,d,e,i){if(i==0&&c.maxRank<0&&c.minRank<0){a[c.temp[0]].push(c);c.maxRank=c.temp[0];c.minRank=c.temp[0];c.temp[0]=a[c.maxRank].length-1}if(b!=null&&d!=null&&b.maxRank-c.maxRank>1){d.maxRank=b.maxRank;d.minRank=
-c.maxRank;d.temp=[];d.x=[];d.y=[];for(b=d.minRank+1;b<d.maxRank;b++){a[b].push(d);d.setGeneralPurposeVariable(b,a[b].length-1)}}},c,false,null)};mxGraphHierarchyModel.prototype.visit=function(a,b,c,d){if(b!=null){for(var e=0;e<b.length;e++){var f=b[e];if(f!=null){d==null&&(d={});if(c){f.hashCode=[];f.hashCode[0]=this.dfsCount;f.hashCode[1]=e;this.extendedDfs(null,f,null,a,d,f.hashCode,e,0)}else this.dfs(null,f,null,a,d,0)}}this.dfsCount++}};
-mxGraphHierarchyModel.prototype.dfs=function(a,b,c,d,e,f){if(b!=null){var g=mxCellPath.create(b.cell);if(e[g]==null){e[g]=b;d(a,b,c,f,0);a=b.connectsAsSource.slice();for(c=0;c<a.length;c++){g=a[c];this.dfs(b,g.target,g,d,e,f+1)}}else d(a,b,c,f,1)}};
-mxGraphHierarchyModel.prototype.extendedDfs=function(a,b,c,d,e,f,g,h){if(b!=null){if(a!=null&&(b.hashCode==null||b.hashCode[0]!=a.hashCode[0])){f=a.hashCode.length+1;b.hashCode=a.hashCode.slice();b.hashCode[f-1]=g}g=mxCellPath.create(b.cell);if(e[g]==null){e[g]=b;d(a,b,c,h,0);a=b.connectsAsSource.slice();for(c=0;c<a.length;c++){g=a[c];this.extendedDfs(b,g.target,g,d,e,b.hashCode,c,h+1)}}else d(a,b,c,h,1)}};function mxHierarchicalLayoutStage(){}mxHierarchicalLayoutStage.prototype.execute=function(){};
-function mxMedianHybridCrossingReduction(a){this.layout=a}mxMedianHybridCrossingReduction.prototype=new mxHierarchicalLayoutStage;mxMedianHybridCrossingReduction.prototype.constructor=mxMedianHybridCrossingReduction;mxMedianHybridCrossingReduction.prototype.layout=null;mxMedianHybridCrossingReduction.prototype.maxIterations=24;mxMedianHybridCrossingReduction.prototype.nestedBestRanks=null;mxMedianHybridCrossingReduction.prototype.currentBestCrossings=0;
-mxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement=0;mxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations=2;
-mxMedianHybridCrossingReduction.prototype.execute=function(){var a=this.layout.getModel();this.nestedBestRanks=[];for(var b=0;b<a.ranks.length;b++)this.nestedBestRanks[b]=a.ranks[b].slice();for(var c=0,d=this.calculateCrossings(a),b=0;b<this.maxIterations&&c<this.maxNoImprovementIterations;b++){this.weightedMedian(b,a);this.transpose(b,a);var e=this.calculateCrossings(a);if(e<d){d=e;for(e=c=0;e<this.nestedBestRanks.length;e++)for(var f=a.ranks[e],g=0;g<f.length;g++){var h=f[g];this.nestedBestRanks[e][h.getGeneralPurposeVariable(e)]=
-h}}else{c++;for(e=0;e<this.nestedBestRanks.length;e++){f=a.ranks[e];for(g=0;g<f.length;g++){h=f[g];h.setGeneralPurposeVariable(e,g)}}}if(d==0)break}c=[];d=[];for(b=0;b<a.maxRank+1;b++){d[b]=[];c[b]=d[b]}for(b=0;b<this.nestedBestRanks.length;b++)for(e=0;e<this.nestedBestRanks[b].length;e++)d[b].push(this.nestedBestRanks[b][e]);a.ranks=c};mxMedianHybridCrossingReduction.prototype.calculateCrossings=function(a){for(var b=a.ranks.length,c=0,d=1;d<b;d++)c=c+this.calculateRankCrossing(d,a);return c};
-mxMedianHybridCrossingReduction.prototype.calculateRankCrossing=function(a,b){for(var c=0,d=b.ranks[a],e=d.length,f=b.ranks[a-1].length,g=[],h=0;h<e;h++)g[h]=[];for(h=0;h<d.length;h++)for(var k=d[h],i=k.getGeneralPurposeVariable(a),l=k.getPreviousLayerConnectedCells(a),k=0;k<l.length;k++){var m=l[k].getGeneralPurposeVariable(a-1);g[i][m]=201207}for(h=0;h<e;h++)for(k=0;k<f;k++)if(g[h][k]==201207){for(d=h+1;d<e;d++)for(i=0;i<k;i++)g[d][i]==201207&&c++;for(d=0;d<h;d++)for(i=k+1;i<f;i++)g[d][i]==201207&&
-c++}return c/2};
-mxMedianHybridCrossingReduction.prototype.transpose=function(a,b){for(var c=true,d=0;c&&d++<10;)for(var e=a%2==1&&d%2==1,c=false,f=0;f<b.ranks.length;f++){for(var g=b.ranks[f],h=[],k=0;k<g.length;k++){var i=g[k],l=i.getGeneralPurposeVariable(f);l<0&&(l=k);h[l]=i}for(var m=l=i=null,n=null,o=null,p=null,q=null,t=null,u=null,v=null,k=0;k<g.length-1;k++){if(k==0){for(var u=h[k],i=u.getNextLayerConnectedCells(f),l=u.getPreviousLayerConnectedCells(f),o=[],p=[],w=0;w<i.length;w++)o[w]=i[w].getGeneralPurposeVariable(f+1);
-for(w=0;w<l.length;w++)p[w]=l[w].getGeneralPurposeVariable(f-1)}else{i=m;l=n;o=q;p=t;u=v}v=h[k+1];m=v.getNextLayerConnectedCells(f);n=v.getPreviousLayerConnectedCells(f);q=[];t=[];for(w=0;w<m.length;w++)q[w]=m[w].getGeneralPurposeVariable(f+1);for(w=0;w<n.length;w++)t[w]=n[w].getGeneralPurposeVariable(f-1);for(var r=0,s=0,w=0;w<o.length;w++)for(var y=0;y<q.length;y++){o[w]>q[y]&&r++;o[w]<q[y]&&s++}for(w=0;w<p.length;w++)for(y=0;y<t.length;y++){p[w]>t[y]&&r++;p[w]<t[y]&&s++}if(s<r||s==r&&e){m=u.getGeneralPurposeVariable(f);
-u.setGeneralPurposeVariable(f,v.getGeneralPurposeVariable(f));v.setGeneralPurposeVariable(f,m);m=i;n=l;q=o;t=p;v=u;e||(c=true)}}}};mxMedianHybridCrossingReduction.prototype.weightedMedian=function(a,b){var c=a%2==0;if(c)for(var d=b.maxRank-1;d>=0;d--)this.medianRank(d,c);else for(d=1;d<b.maxRank;d++)this.medianRank(d,c)};
-mxMedianHybridCrossingReduction.prototype.medianRank=function(a,b){for(var c=this.nestedBestRanks[a].length,d=[],e=[],f=0;f<c;f++){var g=this.nestedBestRanks[a][f],h=new MedianCellSorter;h.cell=g;var k;k=b?g.getNextLayerConnectedCells(a):g.getPreviousLayerConnectedCells(a);var i;i=b?a+1:a-1;if(k!=null&&k.length!=0){h.medianValue=this.medianValue(k,i);d.push(h)}else e[g.getGeneralPurposeVariable(a)]=true}d.sort(MedianCellSorter.prototype.compare);for(f=0;f<c;f++)if(e[f]==null){g=d.shift().cell;g.setGeneralPurposeVariable(a,
-f)}};mxMedianHybridCrossingReduction.prototype.medianValue=function(a,b){for(var c=[],d=0,e=0;e<a.length;e++){var f=a[e];c[d++]=f.getGeneralPurposeVariable(b)}c.sort(function(a,b){return a-b});if(d%2==1)return c[Math.floor(d/2)];if(d==2)return(c[0]+c[1])/2;e=d/2;f=c[e-1]-c[0];d=c[d-1]-c[e];return(c[e-1]*d+c[e]*f)/(f+d)};function MedianCellSorter(){}MedianCellSorter.prototype.medianValue=0;MedianCellSorter.prototype.cell=!1;
-MedianCellSorter.prototype.compare=function(a,b){return a!=null&&b!=null?b.medianValue>a.medianValue?-1:b.medianValue<a.medianValue?1:0:0};function mxMinimumCycleRemover(a){this.layout=a}mxMinimumCycleRemover.prototype=new mxHierarchicalLayoutStage;mxMinimumCycleRemover.prototype.constructor=mxMinimumCycleRemover;mxMinimumCycleRemover.prototype.layout=null;
-mxMinimumCycleRemover.prototype.execute=function(){var a=this.layout.getModel(),b={},c=mxUtils.clone(a.vertexMapper,null,true),d=null;if(a.roots!=null)for(var e=a.roots,d=[],f=0;f<e.length;f++){var g=mxCellPath.create(e[f]);d[f]=a.vertexMapper[g]}a.visit(function(a,d,e){if(d.isAncestor(a)){e.invert();mxUtils.remove(e,a.connectsAsSource);a.connectsAsTarget.push(e);mxUtils.remove(e,d.connectsAsTarget);d.connectsAsSource.push(e)}a=mxCellPath.create(d.cell);b[a]=d;delete c[a]},d,true,null);d=null;c.lenth>
-0&&(d=mxUtils.clone(c,null,true));f=mxUtils.clone(b,null,true);a.visit(function(a,d,e){if(d.isAncestor(a)){e.invert();mxUtils.remove(e,a.connectsAsSource);d.connectsAsSource.push(e);a.connectsAsTarget.push(e);mxUtils.remove(e,d.connectsAsTarget)}a=mxCellPath.create(d.cell);b[a]=d;delete c[a]},c,true,f);e=this.layout.getGraph();if(d!=null&&d.length>0){a=a.roots;for(f=0;f<d.length;f++){g=d[f].cell;e.getIncomingEdges(g).length==0&&a.push(g)}}};
-function mxCoordinateAssignment(a,b,c,d,e,f){this.layout=a;this.intraCellSpacing=b;this.interRankCellSpacing=c;this.orientation=d;this.initialX=e;this.parallelEdgeSpacing=f}var mxHierarchicalEdgeStyle={ORTHOGONAL:1,POLYLINE:2,STRAIGHT:3};mxCoordinateAssignment.prototype=new mxHierarchicalLayoutStage;mxCoordinateAssignment.prototype.constructor=mxCoordinateAssignment;mxCoordinateAssignment.prototype.layout=null;mxCoordinateAssignment.prototype.intraCellSpacing=30;
-mxCoordinateAssignment.prototype.interRankCellSpacing=10;mxCoordinateAssignment.prototype.parallelEdgeSpacing=10;mxCoordinateAssignment.prototype.maxIterations=8;mxCoordinateAssignment.prototype.prefHozEdgeSep=5;mxCoordinateAssignment.prototype.prefVertEdgeOff=2;mxCoordinateAssignment.prototype.minEdgeJetty=12;mxCoordinateAssignment.prototype.channelBuffer=4;mxCoordinateAssignment.prototype.jettyPositions=null;mxCoordinateAssignment.prototype.orientation=mxConstants.DIRECTION_NORTH;
-mxCoordinateAssignment.prototype.initialX=null;mxCoordinateAssignment.prototype.limitX=null;mxCoordinateAssignment.prototype.currentXDelta=null;mxCoordinateAssignment.prototype.widestRank=null;mxCoordinateAssignment.prototype.rankTopY=null;mxCoordinateAssignment.prototype.rankBottomY=null;mxCoordinateAssignment.prototype.widestRankValue=null;mxCoordinateAssignment.prototype.rankWidths=null;mxCoordinateAssignment.prototype.rankY=null;mxCoordinateAssignment.prototype.fineTuning=!0;
-mxCoordinateAssignment.prototype.edgeStyle=mxHierarchicalEdgeStyle.POLYLINE;mxCoordinateAssignment.prototype.nextLayerConnectedCache=null;mxCoordinateAssignment.prototype.previousLayerConnectedCache=null;mxCoordinateAssignment.prototype.groupPadding=10;
-mxCoordinateAssignment.prototype.printStatus=function(){var a=this.layout.getModel();mxLog.show();mxLog.writeln("======Coord assignment debug=======");for(var b=0;b<a.ranks.length;b++){mxLog.write("Rank ",b," : ");for(var c=a.ranks[b],d=0;d<c.length;d++)mxLog.write(c[d].getGeneralPurposeVariable(b)," ");mxLog.writeln()}mxLog.writeln("====================================")};
-mxCoordinateAssignment.prototype.execute=function(){this.jettyPositions=[];var a=this.layout.getModel();this.currentXDelta=0;this.initialCoords(this.layout.getGraph(),a);this.fineTuning&&this.minNode(a);var b=1E8;if(this.fineTuning)for(var c=0;c<this.maxIterations;c++){if(c!=0){this.medianPos(c,a);this.minNode(a)}if(this.currentXDelta<b){for(var d=0;d<a.ranks.length;d++)for(var e=a.ranks[d],f=0;f<e.length;f++){var g=e[f];g.setX(d,g.getGeneralPurposeVariable(d))}b=this.currentXDelta}else for(d=0;d<
-a.ranks.length;d++){e=a.ranks[d];for(f=0;f<e.length;f++){g=e[f];g.setGeneralPurposeVariable(d,g.getX(d))}}this.minPath(this.layout.getGraph(),a);this.currentXDelta=0}this.setCellLocations(this.layout.getGraph(),a)};
-mxCoordinateAssignment.prototype.minNode=function(a){for(var b=[],c=[],d=[],e=0;e<=a.maxRank;e++){d[e]=a.ranks[e];for(var f=0;f<d[e].length;f++){var g=d[e][f],h=new WeightedCellSorter(g,e);h.rankIndex=f;h.visited=true;b.push(h);g=mxCellPath.create(g.getCoreCell());c[g]=h}}a=b.length*10;for(f=0;b.length>0&&f<=a;){var h=b.shift(),e=h.cell,k=h.weightedValue,i=parseInt(h.rankIndex),g=e.getNextLayerConnectedCells(k),l=e.getPreviousLayerConnectedCells(k),m=g.length,n=l.length,o=this.medianXValue(g,k+1),
-p=this.medianXValue(l,k-1),q=m+n,t=e.getGeneralPurposeVariable(k),u=t;q>0&&(u=(o*m+p*n)/q);m=false;if(u<t-1)if(i==0){e.setGeneralPurposeVariable(k,u);m=true}else{i=d[k][i-1];t=i.getGeneralPurposeVariable(k);t=t+i.width/2+this.intraCellSpacing+e.width/2;if(t<u){e.setGeneralPurposeVariable(k,u);m=true}else if(t<e.getGeneralPurposeVariable(k)-1){e.setGeneralPurposeVariable(k,t);m=true}}else if(u>t+1)if(i==d[k].length-1){e.setGeneralPurposeVariable(k,u);m=true}else{i=d[k][i+1];t=i.getGeneralPurposeVariable(k);
-t=t-i.width/2-this.intraCellSpacing-e.width/2;if(t>u){e.setGeneralPurposeVariable(k,u);m=true}else if(t>e.getGeneralPurposeVariable(k)+1){e.setGeneralPurposeVariable(k,t);m=true}}if(m){for(e=0;e<g.length;e++){k=g[e];k=mxCellPath.create(k.getCoreCell());k=c[k];if(k!=null&&k.visited==false){k.visited=true;b.push(k)}}for(e=0;e<l.length;e++){k=l[e];k=mxCellPath.create(k.getCoreCell());k=c[k];if(k!=null&&k.visited==false){k.visited=true;b.push(k)}}}h.visited=false;f++}};
-mxCoordinateAssignment.prototype.medianPos=function(a,b){if(a%2==0)for(var c=b.maxRank;c>0;c--)this.rankMedianPosition(c-1,b,c);else for(c=0;c<b.maxRank-1;c++)this.rankMedianPosition(c+1,b,c)};
-mxCoordinateAssignment.prototype.rankMedianPosition=function(a,b,c){for(var b=b.ranks[a],d=[],e=[],f=0;f<b.length;f++){var g=b[f];d[f]=new WeightedCellSorter;d[f].cell=g;d[f].rankIndex=f;var h=mxCellPath.create(g.getCoreCell());e[h]=d[f];var k=null,k=c<a?g.getPreviousLayerConnectedCells(a):g.getNextLayerConnectedCells(a);d[f].weightedValue=this.calculatedWeightedValue(g,k)}d.sort(WeightedCellSorter.prototype.compare);for(f=0;f<d.length;f++){h=0;g=d[f].cell;h=0;k=c<a?g.getPreviousLayerConnectedCells(a).slice():
-g.getNextLayerConnectedCells(a).slice();if(k!=null){h=k.length;h=h>0?this.medianXValue(k,c):g.getGeneralPurposeVariable(a)}for(var i=0,k=-1E8,l=d[f].rankIndex-1;l>=0;){var m=mxCellPath.create(b[l].getCoreCell()),m=e[m];if(m!=null){var n=m.cell;if(m.visited){k=n.getGeneralPurposeVariable(a)+n.width/2+this.intraCellSpacing+i+g.width/2;l=-1}else{i=i+(n.width+this.intraCellSpacing);l--}}}i=0;n=1E8;for(l=d[f].rankIndex+1;l<d.length;){m=mxCellPath.create(b[l].getCoreCell());m=e[m];if(m!=null){var o=m.cell;
-if(m.visited){n=o.getGeneralPurposeVariable(a)-o.width/2-this.intraCellSpacing-i-g.width/2;l=d.length}else{i=i+(o.width+this.intraCellSpacing);l++}}}if(h>=k&&h<=n)g.setGeneralPurposeVariable(a,h);else if(h<k){g.setGeneralPurposeVariable(a,k);this.currentXDelta=this.currentXDelta+(k-h)}else if(h>n){g.setGeneralPurposeVariable(a,n);this.currentXDelta=this.currentXDelta+(h-n)}d[f].visited=true}};
-mxCoordinateAssignment.prototype.calculatedWeightedValue=function(a,b){for(var c=0,d=0;d<b.length;d++){var e=b[d];a.isVertex()&&e.isVertex()?c++:c=a.isEdge()&&e.isEdge()?c+8:c+2}return c};mxCoordinateAssignment.prototype.medianXValue=function(a,b){if(a.length==0)return 0;for(var c=[],d=0;d<a.length;d++)c[d]=a[d].getGeneralPurposeVariable(b);c.sort(function(a,b){return a-b});if(a.length%2==1)return c[Math.floor(a.length/2)];d=a.length/2;return(c[d-1]+c[d])/2};
-mxCoordinateAssignment.prototype.initialCoords=function(a,b){this.calculateWidestRank(a,b);for(var c=this.widestRank;c>=0;c--)c<b.maxRank&&this.rankCoordinates(c,a,b);for(c=this.widestRank+1;c<=b.maxRank;c++)c>0&&this.rankCoordinates(c,a,b)};
-mxCoordinateAssignment.prototype.rankCoordinates=function(a,b,c){for(var b=c.ranks[a],c=0,d=this.initialX+(this.widestRankValue-this.rankWidths[a])/2,e=false,f=0;f<b.length;f++){var g=b[f];if(g.isVertex()){var h=this.layout.getVertexBounds(g.cell);if(h!=null)if(this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH){g.width=h.width;g.height=h.height}else{g.width=h.height;g.height=h.width}else e=true;c=Math.max(c,g.height)}else if(g.isEdge()){h=1;g.edges!=null?
-h=g.edges.length:mxLog.warn("edge.edges is null");g.width=(h-1)*this.parallelEdgeSpacing}d=d+g.width/2;g.setX(a,d);g.setGeneralPurposeVariable(a,d);d=d+g.width/2;d=d+this.intraCellSpacing}e==true&&mxLog.warn("At least one cell has no bounds")};
-mxCoordinateAssignment.prototype.calculateWidestRank=function(a,b){var c=-this.interRankCellSpacing,d=0;this.rankWidths=[];this.rankY=[];for(var e=b.maxRank;e>=0;e--){for(var f=0,g=b.ranks[e],h=this.initialX,k=false,i=0;i<g.length;i++){var l=g[i];if(l.isVertex()){var m=this.layout.getVertexBounds(l.cell);if(m!=null)if(this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH){l.width=m.width;l.height=m.height}else{l.width=m.height;l.height=m.width}else k=true;f=
-Math.max(f,l.height)}else if(l.isEdge()){m=1;l.edges!=null?m=l.edges.length:mxLog.warn("edge.edges is null");l.width=(m-1)*this.parallelEdgeSpacing}h=h+l.width/2;l.setX(e,h);l.setGeneralPurposeVariable(e,h);h=h+l.width/2;h=h+this.intraCellSpacing;if(h>this.widestRankValue){this.widestRankValue=h;this.widestRank=e}this.rankWidths[e]=h}k==true&&mxLog.warn("At least one cell has no bounds");this.rankY[e]=c;h=f/2+d/2+this.interRankCellSpacing;d=f;c=this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==
-mxConstants.DIRECTION_WEST?c+h:c-h;for(i=0;i<g.length;i++)g[i].setY(e,c)}};
-mxCoordinateAssignment.prototype.minPath=function(a,b){var c=b.edgeMapper,d;for(d in c){var e=c[d];if(!(e.maxRank-e.minRank-1<1)){for(var f=e.getGeneralPurposeVariable(e.minRank+1),g=true,h=0,k=e.minRank+2;k<e.maxRank;k++){var i=e.getGeneralPurposeVariable(k);if(f!=i){g=false;f=i}else h++}if(!g){for(var g=f=0,i=[],l=[],m=e.getGeneralPurposeVariable(e.minRank+1),k=e.minRank+1;k<e.maxRank-1;k++){var n=e.getX(k+1);if(m==n){i[k-e.minRank-1]=m;f++}else if(this.repositionValid(b,e,k+1,m)){i[k-e.minRank-
-1]=m;f++}else m=i[k-e.minRank-1]=n}m=e.getX(k);for(k=e.maxRank-1;k>e.minRank+1;k--){n=e.getX(k-1);if(m==n){l[k-e.minRank-2]=m;g++}else if(this.repositionValid(b,e,k-1,m)){l[k-e.minRank-2]=m;g++}else{l[k-e.minRank-2]=e.getX(k-1);m=n}}if(g>h||f>h)if(g>=f)for(k=e.maxRank-2;k>e.minRank;k--)e.setX(k,l[k-e.minRank-1]);else if(f>g)for(k=e.minRank+2;k<e.maxRank;k++)e.setX(k,i[k-e.minRank-2])}}}};
-mxCoordinateAssignment.prototype.repositionValid=function(a,b,c,d){for(var a=a.ranks[c],e=-1,f=0;f<a.length;f++)if(b==a[f]){e=f;break}if(e<0)return false;f=b.getGeneralPurposeVariable(c);if(d<f){if(e==0)return true;a=a[e-1];c=a.getGeneralPurposeVariable(c);c=c+a.width/2+this.intraCellSpacing+b.width/2;if(!(c<=d))return false}else if(d>f){if(e==a.length-1)return true;a=a[e+1];c=a.getGeneralPurposeVariable(c);c=c-a.width/2-this.intraCellSpacing-b.width/2;if(!(c>=d))return false}return true};
-mxCoordinateAssignment.prototype.setCellLocations=function(a,b){this.rankTopY=[];this.rankBottomY=[];for(var c=0;c<b.ranks.length;c++){this.rankTopY[c]=Number.MAX_VALUE;this.rankBottomY[c]=0}c=null;this.layout.resizeParent&&(c={});var d=b.edgeMapper,e=b.vertexMapper,f;for(f in e){var g=e[f];this.setVertexLocation(g);if(this.layout.resizeParent){var g=a.model.getParent(g.cell),h=mxCellPath.create(g);c[h]==null&&(c[h]=g)}}this.layout.resizeParent&&c!=null&&this.adjustParents(c);(this.edgeStyle==mxHierarchicalEdgeStyle.ORTHOGONAL||
-this.edgeStyle==mxHierarchicalEdgeStyle.POLYLINE)&&this.localEdgeProcessing(b);for(f in d)this.setEdgePosition(d[f])};mxCoordinateAssignment.prototype.adjustParents=function(a){var b=[],c;for(c in a)b.push(a[c]);this.layout.arrangeGroups(mxUtils.sortCells(b,true),this.groupPadding)};
-mxCoordinateAssignment.prototype.localEdgeProcessing=function(a){for(var b=0;b<a.ranks.length;b++)for(var c=a.ranks[b],d=0;d<c.length;d++){var e=c[d];if(e.isVertex())for(var f=e.getPreviousLayerConnectedCells(b),g=b-1,h=0;h<2;h++){if(g>-1&&g<a.ranks.length&&f!=null&&f.length>0){for(var k=[],i=0;i<f.length;i++){var l=new WeightedCellSorter(f[i],f[i].getX(g));k.push(l)}k.sort(WeightedCellSorter.prototype.compare);for(var l=e.x[0]-e.width/2,m=l+e.width,n=f=0,g=[],i=0;i<k.length;i++){var o=k[i].cell,
-p;if(o.isVertex()){p=h==0?e.connectsAsSource:e.connectsAsTarget;for(var q=0;q<p.length;q++)if(p[q].source==o||p[q].target==o){f=f+p[q].edges.length;n++;g.push(p[q])}}else{f=f+o.edges.length;n++;g.push(o)}}if(e.width>(f+1)*this.prefHozEdgeSep+2*this.prefHozEdgeSep){l=l+this.prefHozEdgeSep;m=m-this.prefHozEdgeSep}k=(m-l)/f;l=l+k/2;m=this.minEdgeJetty-this.prefVertEdgeOff;for(i=n=0;i<g.length;i++){o=g[i].edges.length;q=mxCellPath.create(g[i].edges[0]);p=this.jettyPositions[q];if(p==null){p=[];this.jettyPositions[q]=
-p}i<f/2?m=m+this.prefVertEdgeOff:i>f/2&&(m=m-this.prefVertEdgeOff);for(q=0;q<o;q++){p[q*4+h*2]=l;l=l+k;p[q*4+h*2+1]=m}n=Math.max(n,m)}}f=e.getNextLayerConnectedCells(b);g=b+1}}};
-mxCoordinateAssignment.prototype.setEdgePosition=function(a){var b=0;if(a.temp[0]!=101207){var c=a.maxRank,d=a.minRank;if(c==d){c=a.source.maxRank;d=a.target.minRank}for(var e=0,f=this.jettyPositions[mxCellPath.create(a.edges[0])],g=a.isReversed?a.target.cell:a.source.cell,h=0;h<a.edges.length;h++){var k=a.edges[h],i=this.layout.graph.view.getVisibleTerminal(k,true),l=[],m=a.isReversed;i!=g&&(m=!m);if(f!=null){var i=m?2:0,n=m?this.rankTopY[d]:this.rankBottomY[c],o=f[e*4+1+i];m&&(o=-o);n=n+o;i=f[e*
-4+i];this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH?l.push(new mxPoint(i,n)):l.push(new mxPoint(n,i))}var p=a.x.length-1,n=i=-1,o=a.maxRank-1;if(m){p=0;i=a.x.length;n=1;o=a.minRank+1}for(;a.maxRank!=a.minRank&&p!=i;p=p+n){var q=a.x[p]+b,t=(this.rankTopY[o]+this.rankBottomY[o+1])/2,u=(this.rankTopY[o-1]+this.rankBottomY[o])/2;if(m)var v=t,t=u,u=v;if(this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH){l.push(new mxPoint(q,
-t));l.push(new mxPoint(q,u))}else{l.push(new mxPoint(t,q));l.push(new mxPoint(u,q))}this.limitX=Math.max(this.limitX,q);o=o+n}if(f!=null){i=m?2:0;n=m?this.rankBottomY[c]:this.rankTopY[d];o=f[e*4+3-i];m&&(o=-o);n=n-o;i=f[e*4+2-i];this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH?l.push(new mxPoint(i,n)):l.push(new mxPoint(n,i))}a.isReversed&&this.processReversedEdge(a,k);this.layout.setEdgePoints(k,l);b=b==0?this.parallelEdgeSpacing:b>0?-b:-b+this.parallelEdgeSpacing;
-e++}a.temp[0]=101207}};mxCoordinateAssignment.prototype.setVertexLocation=function(a){var b=a.cell,c=a.x[0]-a.width/2,d=a.y[0]-a.height/2;this.rankTopY[a.minRank]=Math.min(this.rankTopY[a.minRank],d);this.rankBottomY[a.minRank]=Math.max(this.rankBottomY[a.minRank],d+a.height);this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH?this.layout.setVertexLocation(b,c,d):this.layout.setVertexLocation(b,d,c);this.limitX=Math.max(this.limitX,c+a.width)};
-mxCoordinateAssignment.prototype.processReversedEdge=function(){};function WeightedCellSorter(a,b){this.cell=a;this.weightedValue=b}WeightedCellSorter.prototype.weightedValue=0;WeightedCellSorter.prototype.nudge=!1;WeightedCellSorter.prototype.visited=!1;WeightedCellSorter.prototype.rankIndex=null;WeightedCellSorter.prototype.cell=null;WeightedCellSorter.prototype.compare=function(a,b){return a!=null&&b!=null?b.weightedValue>a.weightedValue?-1:b.weightedValue<a.weightedValue?1:b.nudge?-1:1:0};
-function mxHierarchicalLayout(a,b,c){mxGraphLayout.call(this,a);this.orientation=b!=null?b:mxConstants.DIRECTION_NORTH;this.deterministic=c!=null?c:true}mxHierarchicalLayout.prototype=new mxGraphLayout;mxHierarchicalLayout.prototype.constructor=mxHierarchicalLayout;mxHierarchicalLayout.prototype.roots=null;mxHierarchicalLayout.prototype.resizeParent=!1;mxHierarchicalLayout.prototype.moveParent=!1;mxHierarchicalLayout.prototype.parentBorder=0;mxHierarchicalLayout.prototype.intraCellSpacing=30;
-mxHierarchicalLayout.prototype.interRankCellSpacing=50;mxHierarchicalLayout.prototype.interHierarchySpacing=60;mxHierarchicalLayout.prototype.parallelEdgeSpacing=10;mxHierarchicalLayout.prototype.orientation=mxConstants.DIRECTION_NORTH;mxHierarchicalLayout.prototype.fineTuning=!0;mxHierarchicalLayout.prototype.tightenToSource=!0;mxHierarchicalLayout.prototype.disableEdgeStyle=!0;mxHierarchicalLayout.prototype.promoteEdges=!0;mxHierarchicalLayout.prototype.traverseAncestors=!0;
-mxHierarchicalLayout.prototype.model=null;mxHierarchicalLayout.prototype.getModel=function(){return this.model};mxHierarchicalLayout.prototype.execute=function(a,b){this.parent=a;var c=this.graph.model;if(!(b==null&&a==null)){if(b!=null&&a!=null){for(var d=[],e=0;e<b.length;e++)c.isAncestor(a,b[e])&&d.push(b[e]);this.roots=d}else this.roots=b;c.beginUpdate();try{this.run(a);this.resizeParent&&!this.graph.isCellCollapsed(a)&&this.graph.updateGroupBounds([a],this.parentBorder,this.moveParent)}finally{c.endUpdate()}}};
-mxHierarchicalLayout.prototype.findRoots=function(a,b){var c=[];if(a!=null&&b!=null){var d=this.graph.model,e=null,f=-1E5,g;for(g in b){var h=b[g];if(d.isVertex(h)&&this.graph.isCellVisible(h)){for(var k=this.getEdges(h),i=0,l=0,m=0;m<k.length;m++)this.graph.view.getVisibleTerminal(k[m],true)==h?i++:l++;l==0&&i>0&&c.push(h);k=i-l;if(k>f){f=k;e=h}}}c.length==0&&e!=null&&c.push(e)}return c};
-mxHierarchicalLayout.prototype.getEdges=function(a){for(var b=this.graph.model,c=[],d=this.graph.isCellCollapsed(a),e=b.getChildCount(a),f=0;f<e;f++){var g=b.getChildAt(a,f);if(d||!this.graph.isCellVisible(g))c=c.concat(b.getEdges(g,true,true))}c=c.concat(b.getEdges(a,true,true));b=[];for(f=0;f<c.length;f++){e=this.graph.view.getState(c[f]);d=e!=null?e.getVisibleTerminal(true):this.graph.view.getVisibleTerminal(c[f],true);e=e!=null?e.getVisibleTerminal(false):this.graph.view.getVisibleTerminal(c[f],
-false);(d==e||d!=e&&(e==a&&(this.parent==null||this.graph.isValidAncestor(d,this.parent,this.traverseAncestors))||d==a&&(this.parent==null||this.graph.isValidAncestor(e,this.parent,this.traverseAncestors))))&&b.push(c[f])}return b};
-mxHierarchicalLayout.prototype.run=function(a){var b=[],c=[];if(this.roots==null&&a!=null){var d=this.filterDescendants(a);this.roots=[];var e=true,f;for(f in d)if(d[f]!=null){e=false;break}for(;!e;){for(var g=this.findRoots(a,d),e=0;e<g.length;e++){var h=[];b.push(h);this.traverse(g[e],true,null,c,h,b,d)}for(e=0;e<g.length;e++)this.roots.push(g[e]);e=true;for(f in d)if(d[f]!=null){e=false;break}}}else for(e=0;e<roots.length;e++){h=[];b.push(h);traverse(roots.get(e),true,null,c,h,b,null)}for(e=c=
-0;e<b.length;e++){h=b[e];d=[];for(f in h)d.push(h[f]);this.model=new mxGraphHierarchyModel(this,d,this.roots,a,this.tightenToSource);this.cycleStage(a);this.layeringStage();this.crossingStage(a);c=this.placementStage(c,a)}};
-mxHierarchicalLayout.prototype.filterDescendants=function(a){var b=this.graph.model,c=[];b.isVertex(a)&&(a!=this.parent&&this.graph.isCellVisible(a))&&c.push(a);if(this.traverseAncestors||a==this.parent&&this.graph.isCellVisible(a))for(var d=b.getChildCount(a),e=0;e<d;e++)for(var f=this.filterDescendants(b.getChildAt(a,e)),g=0;g<f.length;g++)c[mxCellPath.create(f[g])]=f[g];return c};
-mxHierarchicalLayout.prototype.traverse=function(a,b,c,d,e,f,g){var h=this.graph.view,k=this.graph.model;if(a!=null&&d!=null){var i=mxCellPath.create(a);if(d[i]==null&&(g==null||g[i]!=null)){e[i]==null&&(e[i]=a);d[i]==null&&(d[i]=a);delete g[i];var l=k.getEdgeCount(a);if(l>0)for(c=0;c<l;c++){var i=k.getEdgeAt(a,c),m=h.getVisibleTerminal(i,true)==a;if(!b||m)e=this.traverse(h.getVisibleTerminal(i,!m),b,i,d,e,f,g)}}else if(e[i]==null)for(c=0;c<f.length;c++){a=f[c];if(a[i]!=null){for(l in e)a[l]=e[l];
-f.pop();return a}}}return e};mxHierarchicalLayout.prototype.cycleStage=function(a){(new mxMinimumCycleRemover(this)).execute(a)};mxHierarchicalLayout.prototype.layeringStage=function(){this.model.initialRank();this.model.fixRanks()};mxHierarchicalLayout.prototype.crossingStage=function(a){(new mxMedianHybridCrossingReduction(this)).execute(a)};
-mxHierarchicalLayout.prototype.placementStage=function(a,b){var c=new mxCoordinateAssignment(this,this.intraCellSpacing,this.interRankCellSpacing,this.orientation,a,this.parallelEdgeSpacing);c.fineTuning=this.fineTuning;c.execute(b);return c.limitX+this.interHierarchySpacing};function mxGraphModel(a){this.currentEdit=this.createUndoableEdit();a!=null?this.setRoot(a):this.clear()}mxGraphModel.prototype=new mxEventSource;mxGraphModel.prototype.constructor=mxGraphModel;mxGraphModel.prototype.root=null;
-mxGraphModel.prototype.cells=null;mxGraphModel.prototype.maintainEdgeParent=!0;mxGraphModel.prototype.createIds=!0;mxGraphModel.prototype.prefix="";mxGraphModel.prototype.postfix="";mxGraphModel.prototype.nextId=0;mxGraphModel.prototype.currentEdit=null;mxGraphModel.prototype.updateLevel=0;mxGraphModel.prototype.endingUpdate=!1;mxGraphModel.prototype.clear=function(){this.setRoot(this.createRoot())};mxGraphModel.prototype.isCreateIds=function(){return this.createIds};
-mxGraphModel.prototype.setCreateIds=function(a){this.createIds=a};mxGraphModel.prototype.createRoot=function(){var a=new mxCell;a.insert(new mxCell);return a};mxGraphModel.prototype.getCell=function(a){return this.cells!=null?this.cells[a]:null};mxGraphModel.prototype.filterCells=function(a,b){var c=null;if(a!=null)for(var c=[],d=0;d<a.length;d++)b(a[d])&&c.push(a[d]);return c};mxGraphModel.prototype.getDescendants=function(a){return this.filterDescendants(null,a)};
-mxGraphModel.prototype.filterDescendants=function(a,b){var c=[],b=b||this.getRoot();(a==null||a(b))&&c.push(b);for(var d=this.getChildCount(b),e=0;e<d;e++)var f=this.getChildAt(b,e),c=c.concat(this.filterDescendants(a,f));return c};mxGraphModel.prototype.getRoot=function(a){var b=a||this.root;if(a!=null)for(;a!=null;){b=a;a=this.getParent(a)}return b};mxGraphModel.prototype.setRoot=function(a){this.execute(new mxRootChange(this,a));return a};
-mxGraphModel.prototype.rootChanged=function(a){var b=this.root;this.root=a;this.nextId=0;this.cells=null;this.cellAdded(a);return b};mxGraphModel.prototype.isRoot=function(a){return a!=null&&this.root==a};mxGraphModel.prototype.isLayer=function(a){return this.isRoot(this.getParent(a))};mxGraphModel.prototype.isAncestor=function(a,b){for(;b!=null&&b!=a;)b=this.getParent(b);return b==a};mxGraphModel.prototype.contains=function(a){return this.isAncestor(this.root,a)};
-mxGraphModel.prototype.getParent=function(a){return a!=null?a.getParent():null};mxGraphModel.prototype.add=function(a,b,c){if(b!=a&&a!=null&&b!=null){c==null&&(c=this.getChildCount(a));var d=a!=this.getParent(b);this.execute(new mxChildChange(this,a,b,c));this.maintainEdgeParent&&d&&this.updateEdgeParents(b)}return b};
-mxGraphModel.prototype.cellAdded=function(a){if(a!=null){a.getId()==null&&this.createIds&&a.setId(this.createId(a));if(a.getId()!=null){var b=this.getCell(a.getId());if(b!=a){for(;b!=null;){a.setId(this.createId(a));b=this.getCell(a.getId())}if(this.cells==null)this.cells={};this.cells[a.getId()]=a}}if(mxUtils.isNumeric(a.getId()))this.nextId=Math.max(this.nextId,a.getId());for(var b=this.getChildCount(a),c=0;c<b;c++)this.cellAdded(this.getChildAt(a,c))}};
-mxGraphModel.prototype.createId=function(){var a=this.nextId;this.nextId++;return this.prefix+a+this.postfix};mxGraphModel.prototype.updateEdgeParents=function(a,b){for(var b=b||this.getRoot(a),c=this.getChildCount(a),d=0;d<c;d++)this.updateEdgeParents(this.getChildAt(a,d),b);for(var e=this.getEdgeCount(a),c=[],d=0;d<e;d++)c.push(this.getEdgeAt(a,d));for(d=0;d<c.length;d++){e=c[d];this.isAncestor(b,e)&&this.updateEdgeParent(e,b)}};
-mxGraphModel.prototype.updateEdgeParent=function(a,b){for(var c=this.getTerminal(a,true),d=this.getTerminal(a,false),e=null;c!=null&&!this.isEdge(c)&&c.geometry!=null&&c.geometry.relative;)c=this.getParent(c);for(;d!=null&&!this.isEdge(d)&&d.geometry!=null&&d.geometry.relative;)d=this.getParent(d);if(this.isAncestor(b,c)&&this.isAncestor(b,d)){e=c==d?this.getParent(c):this.getNearestCommonAncestor(c,d);if(e!=null&&(this.getParent(e)!=this.root||this.isAncestor(e,a))&&this.getParent(a)!=e){c=this.getGeometry(a);
-if(c!=null){var f=this.getOrigin(this.getParent(a)),g=this.getOrigin(e),d=g.x-f.x,f=g.y-f.y,c=c.clone();c.translate(-d,-f);this.setGeometry(a,c)}this.add(e,a,this.getChildCount(e))}}};mxGraphModel.prototype.getOrigin=function(a){var b=null;if(a!=null){b=this.getOrigin(this.getParent(a));if(!this.isEdge(a)){a=this.getGeometry(a);if(a!=null){b.x=b.x+a.x;b.y=b.y+a.y}}}else b=new mxPoint;return b};
-mxGraphModel.prototype.getNearestCommonAncestor=function(a,b){if(a!=null&&b!=null){var c=mxCellPath.create(b);if(c!=null&&c.length>0){var d=a,e=mxCellPath.create(d);if(c.length<e.length)var d=b,f=e,e=c,c=f;for(;d!=null;){f=this.getParent(d);if(c.indexOf(e+mxCellPath.PATH_SEPARATOR)==0&&f!=null)return d;e=mxCellPath.getParentPath(e);d=f}}}return null};mxGraphModel.prototype.remove=function(a){a==this.root?this.setRoot(null):this.getParent(a)!=null&&this.execute(new mxChildChange(this,null,a));return a};
-mxGraphModel.prototype.cellRemoved=function(a){if(a!=null&&this.cells!=null){for(var b=this.getChildCount(a)-1;b>=0;b--)this.cellRemoved(this.getChildAt(a,b));this.cells!=null&&a.getId()!=null&&delete this.cells[a.getId()]}};mxGraphModel.prototype.parentForCellChanged=function(a,b,c){var d=this.getParent(a);if(b!=null)(b!=d||d.getIndex(a)!=c)&&b.insert(a,c);else if(d!=null){c=d.getIndex(a);d.remove(c)}!this.contains(d)&&b!=null?this.cellAdded(a):b==null&&this.cellRemoved(a);return d};
-mxGraphModel.prototype.getChildCount=function(a){return a!=null?a.getChildCount():0};mxGraphModel.prototype.getChildAt=function(a,b){return a!=null?a.getChildAt(b):null};mxGraphModel.prototype.getChildren=function(a){return a!=null?a.children:null};mxGraphModel.prototype.getChildVertices=function(a){return this.getChildCells(a,true,false)};mxGraphModel.prototype.getChildEdges=function(a){return this.getChildCells(a,false,true)};
-mxGraphModel.prototype.getChildCells=function(a,b,c){for(var b=b!=null?b:false,c=c!=null?c:false,d=this.getChildCount(a),e=[],f=0;f<d;f++){var g=this.getChildAt(a,f);(!c&&!b||c&&this.isEdge(g)||b&&this.isVertex(g))&&e.push(g)}return e};mxGraphModel.prototype.getTerminal=function(a,b){return a!=null?a.getTerminal(b):null};
-mxGraphModel.prototype.setTerminal=function(a,b,c){var d=b!=this.getTerminal(a,c);this.execute(new mxTerminalChange(this,a,b,c));this.maintainEdgeParent&&d&&this.updateEdgeParent(a,this.getRoot());return b};mxGraphModel.prototype.setTerminals=function(a,b,c){this.beginUpdate();try{this.setTerminal(a,b,true);this.setTerminal(a,c,false)}finally{this.endUpdate()}};
-mxGraphModel.prototype.terminalForCellChanged=function(a,b,c){var d=this.getTerminal(a,c);b!=null?b.insertEdge(a,c):d!=null&&d.removeEdge(a,c);return d};mxGraphModel.prototype.getEdgeCount=function(a){return a!=null?a.getEdgeCount():0};mxGraphModel.prototype.getEdgeAt=function(a,b){return a!=null?a.getEdgeAt(b):null};mxGraphModel.prototype.getDirectedEdgeCount=function(a,b,c){for(var d=0,e=this.getEdgeCount(a),f=0;f<e;f++){var g=this.getEdgeAt(a,f);g!=c&&this.getTerminal(g,b)==a&&d++}return d};
-mxGraphModel.prototype.getConnections=function(a){return this.getEdges(a,true,true,false)};mxGraphModel.prototype.getIncomingEdges=function(a){return this.getEdges(a,true,false,false)};mxGraphModel.prototype.getOutgoingEdges=function(a){return this.getEdges(a,false,true,false)};
-mxGraphModel.prototype.getEdges=function(a,b,c,d){for(var b=b!=null?b:true,c=c!=null?c:true,d=d!=null?d:true,e=this.getEdgeCount(a),f=[],g=0;g<e;g++){var h=this.getEdgeAt(a,g),k=this.getTerminal(h,true),i=this.getTerminal(h,false);(d&&k==i||k!=i&&(b&&i==a||c&&k==a))&&f.push(h)}return f};
-mxGraphModel.prototype.getEdgesBetween=function(a,b,c){var c=c!=null?c:false,d=this.getEdgeCount(a),e=this.getEdgeCount(b),f=a,g=d;if(e<d){g=e;f=b}d=[];for(e=0;e<g;e++){var h=this.getEdgeAt(f,e),k=this.getTerminal(h,true),i=this.getTerminal(h,false),l=i==a&&k==b;(k==a&&i==b||!c&&l)&&d.push(h)}return d};
-mxGraphModel.prototype.getOpposites=function(a,b,c,d){var c=c!=null?c:true,d=d!=null?d:true,e=[];if(a!=null)for(var f=0;f<a.length;f++){var g=this.getTerminal(a[f],true),h=this.getTerminal(a[f],false);g==b&&h!=null&&h!=b&&d?e.push(h):h==b&&(g!=null&&g!=b&&c)&&e.push(g)}return e};mxGraphModel.prototype.getTopmostCells=function(a){for(var b=[],c=0;c<a.length;c++){for(var d=a[c],e=true,f=this.getParent(d);f!=null;){if(mxUtils.indexOf(a,f)>=0){e=false;break}f=this.getParent(f)}e&&b.push(d)}return b};
-mxGraphModel.prototype.isVertex=function(a){return a!=null?a.isVertex():false};mxGraphModel.prototype.isEdge=function(a){return a!=null?a.isEdge():false};mxGraphModel.prototype.isConnectable=function(a){return a!=null?a.isConnectable():false};mxGraphModel.prototype.getValue=function(a){return a!=null?a.getValue():null};mxGraphModel.prototype.setValue=function(a,b){this.execute(new mxValueChange(this,a,b));return b};mxGraphModel.prototype.valueForCellChanged=function(a,b){return a.valueChanged(b)};
-mxGraphModel.prototype.getGeometry=function(a){return a!=null?a.getGeometry():null};mxGraphModel.prototype.setGeometry=function(a,b){b!=this.getGeometry(a)&&this.execute(new mxGeometryChange(this,a,b));return b};mxGraphModel.prototype.geometryForCellChanged=function(a,b){var c=this.getGeometry(a);a.setGeometry(b);return c};mxGraphModel.prototype.getStyle=function(a){return a!=null?a.getStyle():null};
-mxGraphModel.prototype.setStyle=function(a,b){b!=this.getStyle(a)&&this.execute(new mxStyleChange(this,a,b));return b};mxGraphModel.prototype.styleForCellChanged=function(a,b){var c=this.getStyle(a);a.setStyle(b);return c};mxGraphModel.prototype.isCollapsed=function(a){return a!=null?a.isCollapsed():false};mxGraphModel.prototype.setCollapsed=function(a,b){b!=this.isCollapsed(a)&&this.execute(new mxCollapseChange(this,a,b));return b};
-mxGraphModel.prototype.collapsedStateForCellChanged=function(a,b){var c=this.isCollapsed(a);a.setCollapsed(b);return c};mxGraphModel.prototype.isVisible=function(a){return a!=null?a.isVisible():false};mxGraphModel.prototype.setVisible=function(a,b){b!=this.isVisible(a)&&this.execute(new mxVisibleChange(this,a,b));return b};mxGraphModel.prototype.visibleStateForCellChanged=function(a,b){var c=this.isVisible(a);a.setVisible(b);return c};
-mxGraphModel.prototype.execute=function(a){a.execute();this.beginUpdate();this.currentEdit.add(a);this.fireEvent(new mxEventObject(mxEvent.EXECUTE,"change",a));this.endUpdate()};mxGraphModel.prototype.beginUpdate=function(){this.updateLevel++;this.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE))};
-mxGraphModel.prototype.endUpdate=function(){this.updateLevel--;if(!this.endingUpdate){this.endingUpdate=this.updateLevel==0;this.fireEvent(new mxEventObject(mxEvent.END_UPDATE,"edit",this.currentEdit));try{if(this.endingUpdate&&!this.currentEdit.isEmpty()){this.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO,"edit",this.currentEdit));var a=this.currentEdit;this.currentEdit=this.createUndoableEdit();a.notify();this.fireEvent(new mxEventObject(mxEvent.UNDO,"edit",a))}}finally{this.endingUpdate=false}}};
-mxGraphModel.prototype.createUndoableEdit=function(){var a=new mxUndoableEdit(this,true);a.notify=function(){a.source.fireEvent(new mxEventObject(mxEvent.CHANGE,"edit",a,"changes",a.changes));a.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,"edit",a,"changes",a.changes))};return a};
-mxGraphModel.prototype.mergeChildren=function(a,b,c){c=c!=null?c:true;this.beginUpdate();try{var d={};this.mergeChildrenImpl(a,b,c,d);for(var e in d){var f=d[e],g=this.getTerminal(f,true);if(g!=null){g=d[mxCellPath.create(g)];this.setTerminal(f,g,true)}g=this.getTerminal(f,false);if(g!=null){g=d[mxCellPath.create(g)];this.setTerminal(f,g,false)}}}finally{this.endUpdate()}};
-mxGraphModel.prototype.mergeChildrenImpl=function(a,b,c,d){this.beginUpdate();try{for(var e=a.getChildCount(),f=0;f<e;f++){var g=a.getChildAt(f);if(typeof g.getId=="function"){var h=g.getId(),k=h!=null&&(!this.isEdge(g)||!c)?this.getCell(h):null;if(k==null){var i=g.clone();i.setId(h);i.setTerminal(g.getTerminal(true),true);i.setTerminal(g.getTerminal(false),false);k=b.insert(i);this.cellAdded(k)}d[mxCellPath.create(g)]=k;this.mergeChildrenImpl(g,k,c,d)}}}finally{this.endUpdate()}};
-mxGraphModel.prototype.getParents=function(a){var b=[];if(a!=null)for(var c={},d=0;d<a.length;d++){var e=this.getParent(a[d]);if(e!=null){var f=mxCellPath.create(e);if(c[f]==null){c[f]=e;b.push(e)}}}return b};mxGraphModel.prototype.cloneCell=function(a){return a!=null?this.cloneCells([a],true)[0]:null};
-mxGraphModel.prototype.cloneCells=function(a,b){for(var c={},d=[],e=0;e<a.length;e++)a[e]!=null?d.push(this.cloneCellImpl(a[e],c,b)):d.push(null);for(e=0;e<d.length;e++)d[e]!=null&&this.restoreClone(d[e],a[e],c);return d};mxGraphModel.prototype.cloneCellImpl=function(a,b,c){var d=this.cellCloned(a);b[mxObjectIdentity.get(a)]=d;if(c)for(var c=this.getChildCount(a),e=0;e<c;e++){var f=this.cloneCellImpl(this.getChildAt(a,e),b,true);d.insert(f)}return d};mxGraphModel.prototype.cellCloned=function(a){return a.clone()};
-mxGraphModel.prototype.restoreClone=function(a,b,c){var d=this.getTerminal(b,true);if(d!=null){d=c[mxObjectIdentity.get(d)];d!=null&&d.insertEdge(a,true)}d=this.getTerminal(b,false);if(d!=null){d=c[mxObjectIdentity.get(d)];d!=null&&d.insertEdge(a,false)}for(var d=this.getChildCount(a),e=0;e<d;e++)this.restoreClone(this.getChildAt(a,e),this.getChildAt(b,e),c)};function mxRootChange(a,b){this.model=a;this.previous=this.root=b}
-mxRootChange.prototype.execute=function(){this.root=this.previous;this.previous=this.model.rootChanged(this.previous)};function mxChildChange(a,b,c,d){this.model=a;this.previous=this.parent=b;this.child=c;this.previousIndex=this.index=d}
-mxChildChange.prototype.execute=function(){var a=this.model.getParent(this.child),b=a!=null?a.getIndex(this.child):0;this.previous==null&&this.connect(this.child,false);a=this.model.parentForCellChanged(this.child,this.previous,this.previousIndex);this.previous!=null&&this.connect(this.child,true);this.parent=this.previous;this.previous=a;this.index=this.previousIndex;this.previousIndex=b};
-mxChildChange.prototype.connect=function(a,b){var b=b!=null?b:true,c=a.getTerminal(true),d=a.getTerminal(false);c!=null&&(b?this.model.terminalForCellChanged(a,c,true):this.model.terminalForCellChanged(a,null,true));d!=null&&(b?this.model.terminalForCellChanged(a,d,false):this.model.terminalForCellChanged(a,null,false));a.setTerminal(c,true);a.setTerminal(d,false);c=this.model.getChildCount(a);for(d=0;d<c;d++)this.connect(this.model.getChildAt(a,d),b)};
-function mxTerminalChange(a,b,c,d){this.model=a;this.cell=b;this.previous=this.terminal=c;this.source=d}mxTerminalChange.prototype.execute=function(){this.terminal=this.previous;this.previous=this.model.terminalForCellChanged(this.cell,this.previous,this.source)};function mxValueChange(a,b,c){this.model=a;this.cell=b;this.previous=this.value=c}mxValueChange.prototype.execute=function(){this.value=this.previous;this.previous=this.model.valueForCellChanged(this.cell,this.previous)};
-function mxStyleChange(a,b,c){this.model=a;this.cell=b;this.previous=this.style=c}mxStyleChange.prototype.execute=function(){this.style=this.previous;this.previous=this.model.styleForCellChanged(this.cell,this.previous)};function mxGeometryChange(a,b,c){this.model=a;this.cell=b;this.previous=this.geometry=c}mxGeometryChange.prototype.execute=function(){this.geometry=this.previous;this.previous=this.model.geometryForCellChanged(this.cell,this.previous)};
-function mxCollapseChange(a,b,c){this.model=a;this.cell=b;this.previous=this.collapsed=c}mxCollapseChange.prototype.execute=function(){this.collapsed=this.previous;this.previous=this.model.collapsedStateForCellChanged(this.cell,this.previous)};function mxVisibleChange(a,b,c){this.model=a;this.cell=b;this.previous=this.visible=c}mxVisibleChange.prototype.execute=function(){this.visible=this.previous;this.previous=this.model.visibleStateForCellChanged(this.cell,this.previous)};
-function mxCellAttributeChange(a,b,c){this.cell=a;this.attribute=b;this.previous=this.value=c}mxCellAttributeChange.prototype.execute=function(){var a=this.cell.getAttribute(this.attribute);this.previous==null?this.cell.value.removeAttribute(this.attribute):this.cell.setAttribute(this.attribute,this.previous);this.previous=a};function mxCell(a,b,c){this.value=a;this.setGeometry(b);this.setStyle(c);if(this.onInit!=null)this.onInit()}mxCell.prototype.id=null;mxCell.prototype.value=null;
-mxCell.prototype.geometry=null;mxCell.prototype.style=null;mxCell.prototype.vertex=!1;mxCell.prototype.edge=!1;mxCell.prototype.connectable=!0;mxCell.prototype.visible=!0;mxCell.prototype.collapsed=!1;mxCell.prototype.parent=null;mxCell.prototype.source=null;mxCell.prototype.target=null;mxCell.prototype.children=null;mxCell.prototype.edges=null;mxCell.prototype.mxTransient="id value parent source target children edges".split(" ");mxCell.prototype.getId=function(){return this.id};
-mxCell.prototype.setId=function(a){this.id=a};mxCell.prototype.getValue=function(){return this.value};mxCell.prototype.setValue=function(a){this.value=a};mxCell.prototype.valueChanged=function(a){var b=this.getValue();this.setValue(a);return b};mxCell.prototype.getGeometry=function(){return this.geometry};mxCell.prototype.setGeometry=function(a){this.geometry=a};mxCell.prototype.getStyle=function(){return this.style};mxCell.prototype.setStyle=function(a){this.style=a};mxCell.prototype.isVertex=function(){return this.vertex};
-mxCell.prototype.setVertex=function(a){this.vertex=a};mxCell.prototype.isEdge=function(){return this.edge};mxCell.prototype.setEdge=function(a){this.edge=a};mxCell.prototype.isConnectable=function(){return this.connectable};mxCell.prototype.setConnectable=function(a){this.connectable=a};mxCell.prototype.isVisible=function(){return this.visible};mxCell.prototype.setVisible=function(a){this.visible=a};mxCell.prototype.isCollapsed=function(){return this.collapsed};
-mxCell.prototype.setCollapsed=function(a){this.collapsed=a};mxCell.prototype.getParent=function(){return this.parent};mxCell.prototype.setParent=function(a){this.parent=a};mxCell.prototype.getTerminal=function(a){return a?this.source:this.target};mxCell.prototype.setTerminal=function(a,b){b?this.source=a:this.target=a;return a};mxCell.prototype.getChildCount=function(){return this.children==null?0:this.children.length};mxCell.prototype.getIndex=function(a){return mxUtils.indexOf(this.children,a)};
-mxCell.prototype.getChildAt=function(a){return this.children==null?null:this.children[a]};mxCell.prototype.insert=function(a,b){if(a!=null){if(b==null){b=this.getChildCount();a.getParent()==this&&b--}a.removeFromParent();a.setParent(this);if(this.children==null){this.children=[];this.children.push(a)}else this.children.splice(b,0,a)}return a};mxCell.prototype.remove=function(a){var b=null;if(this.children!=null&&a>=0){b=this.getChildAt(a);if(b!=null){this.children.splice(a,1);b.setParent(null)}}return b};
-mxCell.prototype.removeFromParent=function(){this.parent!=null&&this.parent.remove(this.parent.getIndex(this))};mxCell.prototype.getEdgeCount=function(){return this.edges==null?0:this.edges.length};mxCell.prototype.getEdgeIndex=function(a){return mxUtils.indexOf(this.edges,a)};mxCell.prototype.getEdgeAt=function(a){return this.edges==null?null:this.edges[a]};
-mxCell.prototype.insertEdge=function(a,b){if(a!=null){a.removeFromTerminal(b);a.setTerminal(this,b);if(this.edges==null||a.getTerminal(!b)!=this||mxUtils.indexOf(this.edges,a)<0){if(this.edges==null)this.edges=[];this.edges.push(a)}}return a};mxCell.prototype.removeEdge=function(a,b){if(a!=null){if(a.getTerminal(!b)!=this&&this.edges!=null){var c=this.getEdgeIndex(a);c>=0&&this.edges.splice(c,1)}a.setTerminal(null,b)}return a};
-mxCell.prototype.removeFromTerminal=function(a){var b=this.getTerminal(a);b!=null&&b.removeEdge(this,a)};mxCell.prototype.getAttribute=function(a,b){var c=this.getValue();return(c!=null&&c.nodeType==mxConstants.NODETYPE_ELEMENT?c.getAttribute(a):null)||b};mxCell.prototype.setAttribute=function(a,b){var c=this.getValue();c!=null&&c.nodeType==mxConstants.NODETYPE_ELEMENT&&c.setAttribute(a,b)};
-mxCell.prototype.clone=function(){var a=mxUtils.clone(this,this.mxTransient);a.setValue(this.cloneValue());return a};mxCell.prototype.cloneValue=function(){var a=this.getValue();a!=null&&(typeof a.clone=="function"?a=a.clone():isNaN(a.nodeType)||(a=a.cloneNode(true)));return a};function mxGeometry(a,b,c,d){mxRectangle.call(this,a,b,c,d)}mxGeometry.prototype=new mxRectangle;mxGeometry.prototype.constructor=mxGeometry;mxGeometry.prototype.TRANSLATE_CONTROL_POINTS=!0;
-mxGeometry.prototype.alternateBounds=null;mxGeometry.prototype.sourcePoint=null;mxGeometry.prototype.targetPoint=null;mxGeometry.prototype.points=null;mxGeometry.prototype.offset=null;mxGeometry.prototype.relative=!1;
-mxGeometry.prototype.swap=function(){if(this.alternateBounds!=null){var a=new mxRectangle(this.x,this.y,this.width,this.height);this.x=this.alternateBounds.x;this.y=this.alternateBounds.y;this.width=this.alternateBounds.width;this.height=this.alternateBounds.height;this.alternateBounds=a}};mxGeometry.prototype.getTerminalPoint=function(a){return a?this.sourcePoint:this.targetPoint};mxGeometry.prototype.setTerminalPoint=function(a,b){b?this.sourcePoint=a:this.targetPoint=a;return a};
-mxGeometry.prototype.translate=function(a,b){this.clone();if(!this.relative){this.x=this.x+a;this.y=this.y+b}if(this.sourcePoint!=null){this.sourcePoint.x=this.sourcePoint.x+a;this.sourcePoint.y=this.sourcePoint.y+b}if(this.targetPoint!=null){this.targetPoint.x=this.targetPoint.x+a;this.targetPoint.y=this.targetPoint.y+b}if(this.TRANSLATE_CONTROL_POINTS&&this.points!=null)for(var c=this.points.length,d=0;d<c;d++){var e=this.points[d];if(e!=null){e.x=e.x+a;e.y=e.y+b}}};
-var mxCellPath={PATH_SEPARATOR:".",create:function(a){var b="";if(a!=null)for(var c=a.getParent();c!=null;){b=c.getIndex(a)+mxCellPath.PATH_SEPARATOR+b;a=c;c=a.getParent()}a=b.length;a>1&&(b=b.substring(0,a-1));return b},getParentPath:function(a){if(a!=null){var b=a.lastIndexOf(mxCellPath.PATH_SEPARATOR);if(b>=0)return a.substring(0,b);if(a.length>0)return""}return null},resolve:function(a,b){var c=a;if(b!=null)for(var d=b.split(mxCellPath.PATH_SEPARATOR),e=0;e<d.length;e++)c=c.getChildAt(parseInt(d[e]));
-return c},compare:function(a,b){for(var c=Math.min(a.length,b.length),d=0,e=0;e<c;e++)if(a[e]!=b[e]){if(a[e].length==0||b[e].length==0)d=a[e]==b[e]?0:a[e]>b[e]?1:-1;else{c=parseInt(a[e]);e=parseInt(b[e]);d=c==e?0:c>e?1:-1}break}if(d==0){c=a.length;e=b.length;c!=e&&(d=c>e?1:-1)}return d}},mxPerimeter={RectanglePerimeter:function(a,b,c,d){var b=a.getCenterX(),e=a.getCenterY(),f=Math.atan2(c.y-e,c.x-b),g=new mxPoint(0,0),h=Math.PI,k=Math.PI/2-f,i=Math.atan2(a.height,a.width);if(f<-h+i||f>h-i){g.x=a.x;
-g.y=e-a.width*Math.tan(f)/2}else if(f<-i){g.y=a.y;g.x=b-a.height*Math.tan(k)/2}else if(f<i){g.x=a.x+a.width;g.y=e+a.width*Math.tan(f)/2}else{g.y=a.y+a.height;g.x=b+a.height*Math.tan(k)/2}if(d){if(c.x>=a.x&&c.x<=a.x+a.width)g.x=c.x;else if(c.y>=a.y&&c.y<=a.y+a.height)g.y=c.y;if(c.x<a.x)g.x=a.x;else if(c.x>a.x+a.width)g.x=a.x+a.width;if(c.y<a.y)g.y=a.y;else if(c.y>a.y+a.height)g.y=a.y+a.height}return g},EllipsePerimeter:function(a,b,c,d){var e=a.x,f=a.y,g=a.width/2,h=a.height/2,k=e+g,i=f+h,b=c.x,c=
-c.y,l=parseInt(b-k),m=parseInt(c-i);if(l==0&&m!=0)return new mxPoint(k,i+h*m/Math.abs(m));if(l==0&&m==0)return new mxPoint(b,c);if(d){if(c>=f&&c<=f+a.height){a=c-i;a=Math.sqrt(g*g*(1-a*a/(h*h)))||0;b<=e&&(a=-a);return new mxPoint(k+a,c)}if(b>=e&&b<=e+a.width){a=b-k;a=Math.sqrt(h*h*(1-a*a/(g*g)))||0;c<=f&&(a=-a);return new mxPoint(b,i+a)}}e=m/l;i=i-e*k;f=g*g*e*e+h*h;a=-2*k*f;h=Math.sqrt(a*a-4*f*(g*g*e*e*k*k+h*h*k*k-g*g*h*h));g=(-a+h)/(2*f);h=(-a-h)/(2*f);k=e*g+i;i=e*h+i;e=Math.sqrt(Math.pow(g-b,2)+
-Math.pow(k-c,2));b=Math.sqrt(Math.pow(h-b,2)+Math.pow(i-c,2));f=c=0;if(e<b){c=g;f=k}else{c=h;f=i}return new mxPoint(c,f)},RhombusPerimeter:function(a,b,c,d){var b=a.x,e=a.y,f=a.width,a=a.height,g=b+f/2,h=e+a/2,k=c.x,c=c.y;if(g==k)return h>c?new mxPoint(g,e):new mxPoint(g,e+a);if(h==c)return g>k?new mxPoint(b,h):new mxPoint(b+f,h);var i=g,l=h;d&&(k>=b&&k<=b+f?i=k:c>=e&&c<=e+a&&(l=c));return k<g?c<h?mxUtils.intersection(k,c,i,l,g,e,b,h):mxUtils.intersection(k,c,i,l,g,e+a,b,h):c<h?mxUtils.intersection(k,
-c,i,l,g,e,b+f,h):mxUtils.intersection(k,c,i,l,g,e+a,b+f,h)},TrianglePerimeter:function(a,b,c,d){var b=b!=null?b.style[mxConstants.STYLE_DIRECTION]:null,e=b==mxConstants.DIRECTION_NORTH||b==mxConstants.DIRECTION_SOUTH,f=a.x,g=a.y,h=a.width,a=a.height,k=f+h/2,i=g+a/2,l=new mxPoint(f,g),m=new mxPoint(f+h,i),n=new mxPoint(f,g+a);if(b==mxConstants.DIRECTION_NORTH){l=n;m=new mxPoint(k,g);n=new mxPoint(f+h,g+a)}else if(b==mxConstants.DIRECTION_SOUTH){m=new mxPoint(k,g+a);n=new mxPoint(f+h,g)}else if(b==
-mxConstants.DIRECTION_WEST){l=new mxPoint(f+h,g);m=new mxPoint(f,i);n=new mxPoint(f+h,g+a)}var o=c.x-k,p=c.y-i,o=e?Math.atan2(o,p):Math.atan2(p,o),q=e?Math.atan2(h,a):Math.atan2(a,h),p=false,p=b==mxConstants.DIRECTION_NORTH||b==mxConstants.DIRECTION_WEST?o>-q&&o<q:o<-Math.PI+q||o>Math.PI-q,q=null;if(p)q=d&&(e&&c.x>=l.x&&c.x<=n.x||!e&&c.y>=l.y&&c.y<=n.y)?e?new mxPoint(c.x,l.y):new mxPoint(l.x,c.y):b==mxConstants.DIRECTION_NORTH?new mxPoint(f+h/2+a*Math.tan(o)/2,g+a):b==mxConstants.DIRECTION_SOUTH?
-new mxPoint(f+h/2-a*Math.tan(o)/2,g):b==mxConstants.DIRECTION_WEST?new mxPoint(f+h,g+a/2+h*Math.tan(o)/2):new mxPoint(f,g+a/2-h*Math.tan(o)/2);else{if(d){d=new mxPoint(k,i);if(c.y>=g&&c.y<=g+a){d.x=e?k:b==mxConstants.DIRECTION_WEST?f+h:f;d.y=c.y}else if(c.x>=f&&c.x<=f+h){d.x=c.x;d.y=!e?i:b==mxConstants.DIRECTION_NORTH?g+a:g}k=d.x;i=d.y}q=e&&c.x<=f+h/2||!e&&c.y<=g+a/2?mxUtils.intersection(c.x,c.y,k,i,l.x,l.y,m.x,m.y):mxUtils.intersection(c.x,c.y,k,i,m.x,m.y,n.x,n.y)}q==null&&(q=new mxPoint(k,i));return q}};
-function mxPrintPreview(a,b,c,d,e,f,g,h,k){this.graph=a;this.scale=b!=null?b:1/a.pageScale;this.border=d!=null?d:0;this.pageFormat=c!=null?c:a.pageFormat;this.title=h!=null?h:"Printer-friendly version";this.x0=e!=null?e:0;this.y0=f!=null?f:0;this.borderColor=g;this.pageSelector=k!=null?k:true}mxPrintPreview.prototype.graph=null;mxPrintPreview.prototype.pageFormat=null;mxPrintPreview.prototype.scale=null;mxPrintPreview.prototype.border=0;mxPrintPreview.prototype.x0=0;mxPrintPreview.prototype.y0=0;
-mxPrintPreview.prototype.autoOrigin=!0;mxPrintPreview.prototype.printOverlays=!1;mxPrintPreview.prototype.borderColor=null;mxPrintPreview.prototype.title=null;mxPrintPreview.prototype.pageSelector=null;mxPrintPreview.prototype.wnd=null;mxPrintPreview.prototype.pageCount=0;mxPrintPreview.prototype.getWindow=function(){return this.wnd};mxPrintPreview.prototype.getDoctype=function(){return""};
-mxPrintPreview.prototype.open=function(a){var b=this.graph.cellRenderer.initializeOverlay,c=null;try{if(this.printOverlays)this.graph.cellRenderer.initializeOverlay=function(a,b){b.init(a.view.getDrawPane())};if(this.wnd==null){this.wnd=window.open();var d=this.wnd.document,e=this.getDoctype();e!=null&&e.length>0&&d.writeln(e);d.writeln("<html>");d.writeln("<head>");this.writeHead(d,a);d.writeln("</head>");d.writeln('<body class="mxPage">');mxClient.link("stylesheet",mxClient.basePath+"/css/common.css",
-d);if(mxClient.IS_IE&&document.documentMode!=9){d.namespaces.add("v","urn:schemas-microsoft-com:vml");d.namespaces.add("o","urn:schemas-microsoft-com:office:office");d.createStyleSheet().cssText="v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}";mxClient.link("stylesheet",mxClient.basePath+"/css/explorer.css",d)}var f=this.graph.getGraphBounds().clone(),g=this.graph.getView().getScale(),h=g/this.scale,k=this.graph.getView().getTranslate();if(!this.autoOrigin){this.x0=-k.x*this.scale;
-this.y0=-k.y*this.scale;f.width=f.width+f.x;f.height=f.height+f.y;f.x=0;this.border=f.y=0}f.width=f.width/h;f.height=f.height/h;var i=this.pageFormat.width-this.border*2,l=this.pageFormat.height-this.border*2,m=Math.max(1,Math.ceil((f.width+this.x0)/i)),n=Math.max(1,Math.ceil((f.height+this.y0)/l));this.pageCount=m*n;var o=mxUtils.bind(this,function(){if(this.pageSelector&&(n>1||m>1)){var a=this.createPageSelector(n,m);d.body.appendChild(a);if(mxClient.IS_IE){a.style.position="absolute";var b=function(){a.style.top=
-d.body.scrollTop+10+"px"};mxEvent.addListener(this.wnd,"scroll",function(){b()});mxEvent.addListener(this.wnd,"resize",function(){b()})}}}),p=null;if(mxClient.IS_IE&&document.documentMode!=9){var p=[],q=0,t=false,u=mxImageShape.prototype.scheduleUpdateAspect,v=mxImageShape.prototype.updateAspect,w=function(){if(t&&q==0){mxImageShape.prototype.scheduleUpdateAspect=u;mxImageShape.prototype.updateAspect=v;for(var a="",b=0;b<p.length;b++){a=a+p[b].outerHTML;p[b].parentNode.removeChild(p[b]);b<p.length-
-1&&(a=a+"<hr/>")}d.body.innerHTML=a;o()}};mxImageShape.prototype.scheduleUpdateAspect=function(){q++;u.apply(this,arguments)};mxImageShape.prototype.updateAspect=function(){v.apply(this,arguments);q--;w()}}for(a=0;a<n;a++)for(var r=a*l/this.scale-this.y0/this.scale+(f.y-k.y*g)/g,e=0;e<m;e++){if(this.wnd==null)return null;h=a*m+e+1;c=this.renderPage(this.pageFormat.width,this.pageFormat.height,-(e*i/this.scale-this.x0/this.scale+(f.x-k.x*g)/g),-r,this.scale,h);c.setAttribute("id","mxPage-"+h);if(this.borderColor!=
-null){c.style.borderColor=this.borderColor;c.style.borderStyle="solid";c.style.borderWidth="1px"}c.style.background="white";if(a<n-1||e<m-1)c.style.pageBreakAfter="always";if(mxClient.IS_IE){d.writeln(c.outerHTML);p!=null?p.push(c):c.parentNode.removeChild(c)}else{c.parentNode.removeChild(c);d.body.appendChild(c)}if(a<n-1||e<m-1){var s=d.createElement("hr");s.className="mxPageBreak";d.body.appendChild(s)}}d.writeln("</body>");d.writeln("</html>");d.close();if(p!=null){t=true;w()}else o();mxEvent.release(d.body)}this.wnd.focus()}catch(y){c!=
-null&&c.parentNode!=null&&c.parentNode.removeChild(c)}finally{this.graph.cellRenderer.initializeOverlay=b}return this.wnd};
-mxPrintPreview.prototype.writeHead=function(a,b){this.title!=null&&a.writeln("<title>"+this.title+"</title>");a.writeln('<style type="text/css">');a.writeln("@media print {");a.writeln(" table.mxPageSelector { display: none; }");a.writeln(" hr.mxPageBreak { display: none; }");a.writeln("}");a.writeln("@media screen {");a.writeln(" table.mxPageSelector { position: fixed; right: 10px; top: 10px;font-family: Arial; font-size:10pt; border: solid 1px darkgray;background: white; border-collapse:collapse; }");a.writeln(" table.mxPageSelector td { border: solid 1px gray; padding:4px; }");
-a.writeln(" body.mxPage { background: gray; }");a.writeln("}");b!=null&&a.writeln(b);a.writeln("</style>")};
-mxPrintPreview.prototype.createPageSelector=function(a,b){var c=this.wnd.document,d=c.createElement("table");d.className="mxPageSelector";d.setAttribute("border","0");for(var e=c.createElement("tbody"),f=0;f<a;f++){for(var g=c.createElement("tr"),h=0;h<b;h++){var k=f*b+h+1,i=c.createElement("td");if(!mxClient.IS_NS||mxClient.IS_SF||mxClient.IS_GC){var l=c.createElement("a");l.setAttribute("href","#mxPage-"+k);mxUtils.write(l,k,c);i.appendChild(l)}else mxUtils.write(i,k,c);g.appendChild(i)}e.appendChild(g)}d.appendChild(e);
-return d};
-mxPrintPreview.prototype.renderPage=function(a,b,c,d,e){var f=document.createElement("div");try{f.style.width=a+"px";f.style.height=b+"px";f.style.overflow="hidden";f.style.pageBreakInside="avoid";var g=document.createElement("div");g.style.top=this.border+"px";g.style.left=this.border+"px";g.style.width=a-2*this.border+"px";g.style.height=b-2*this.border+"px";g.style.overflow="hidden";if(this.graph.dialect==mxConstants.DIALECT_VML)g.style.position="absolute";f.appendChild(g);document.body.appendChild(f);var h=
-this.graph.getView(),k=this.graph.container;this.graph.container=g;var i=h.getCanvas(),l=h.getBackgroundPane(),m=h.getDrawPane(),n=h.getOverlayPane();this.graph.dialect==mxConstants.DIALECT_SVG?h.createSvg():this.graph.dialect==mxConstants.DIALECT_VML?h.createVml():h.createHtml();var o=h.isEventsEnabled();h.setEventsEnabled(false);var p=this.graph.isEnabled();this.graph.setEnabled(false);var q=h.getTranslate();h.translate=new mxPoint(c,d);a=null;try{var t=[this.graph.getModel().getRoot()],a=new mxTemporaryCellStates(h,
-e,t)}finally{if(mxClient.IS_IE)h.overlayPane.innerHTML="";else for(var u=g.firstChild;u!=null;){var v=u.nextSibling,w=u.nodeName.toLowerCase();if(w=="svg"){u.setAttribute("width",parseInt(g.style.width));u.setAttribute("height",parseInt(g.style.height))}else u.style.cursor!="default"&&w!="table"&&u.parentNode.removeChild(u);u=v}h.overlayPane.parentNode.removeChild(h.overlayPane);this.graph.setEnabled(p);this.graph.container=k;h.canvas=i;h.backgroundPane=l;h.drawPane=m;h.overlayPane=n;h.translate=
-q;a.destroy();h.setEventsEnabled(o)}}catch(r){f.parentNode.removeChild(f);throw r;}return f};mxPrintPreview.prototype.print=function(){var a=this.open();a!=null&&a.print()};mxPrintPreview.prototype.close=function(){if(this.wnd!=null){this.wnd.close();this.wnd=null}};function mxStylesheet(){this.styles={};this.putDefaultVertexStyle(this.createDefaultVertexStyle());this.putDefaultEdgeStyle(this.createDefaultEdgeStyle())}
-mxStylesheet.prototype.createDefaultVertexStyle=function(){var a={};a[mxConstants.STYLE_SHAPE]=mxConstants.SHAPE_RECTANGLE;a[mxConstants.STYLE_PERIMETER]=mxPerimeter.RectanglePerimeter;a[mxConstants.STYLE_VERTICAL_ALIGN]=mxConstants.ALIGN_MIDDLE;a[mxConstants.STYLE_ALIGN]=mxConstants.ALIGN_CENTER;a[mxConstants.STYLE_FILLCOLOR]="#C3D9FF";a[mxConstants.STYLE_STROKECOLOR]="#6482B9";a[mxConstants.STYLE_FONTCOLOR]="#774400";return a};
-mxStylesheet.prototype.createDefaultEdgeStyle=function(){var a={};a[mxConstants.STYLE_SHAPE]=mxConstants.SHAPE_CONNECTOR;a[mxConstants.STYLE_ENDARROW]=mxConstants.ARROW_CLASSIC;a[mxConstants.STYLE_VERTICAL_ALIGN]=mxConstants.ALIGN_MIDDLE;a[mxConstants.STYLE_ALIGN]=mxConstants.ALIGN_CENTER;a[mxConstants.STYLE_STROKECOLOR]="#6482B9";a[mxConstants.STYLE_FONTCOLOR]="#446299";return a};mxStylesheet.prototype.putDefaultVertexStyle=function(a){this.putCellStyle("defaultVertex",a)};
-mxStylesheet.prototype.putDefaultEdgeStyle=function(a){this.putCellStyle("defaultEdge",a)};mxStylesheet.prototype.getDefaultVertexStyle=function(){return this.styles.defaultVertex};mxStylesheet.prototype.getDefaultEdgeStyle=function(){return this.styles.defaultEdge};mxStylesheet.prototype.putCellStyle=function(a,b){this.styles[a]=b};
-mxStylesheet.prototype.getCellStyle=function(a,b){var c=b;if(a!=null&&a.length>0)for(var d=a.split(";"),c=c!=null&&a.charAt(0)!=";"?mxUtils.clone(c):{},e=0;e<d.length;e++){var f=d[e],g=f.indexOf("=");if(g>=0){var h=f.substring(0,g),f=f.substring(g+1);f==mxConstants.NONE?delete c[h]:c[h]=mxUtils.isNumeric(f)?parseFloat(f):f}else{f=this.styles[f];if(f!=null)for(h in f)c[h]=f[h]}}return c};
-function mxCellState(a,b,c){this.view=a;this.cell=b;this.style=c;this.origin=new mxPoint;this.absoluteOffset=new mxPoint}mxCellState.prototype=new mxRectangle;mxCellState.prototype.constructor=mxCellState;mxCellState.prototype.view=null;mxCellState.prototype.cell=null;mxCellState.prototype.style=null;mxCellState.prototype.invalid=!0;mxCellState.prototype.invalidOrder=!1;mxCellState.prototype.orderChanged=!1;mxCellState.prototype.origin=null;mxCellState.prototype.absolutePoints=null;
-mxCellState.prototype.absoluteOffset=null;mxCellState.prototype.visibleSourceState=null;mxCellState.prototype.visibleTargetState=null;mxCellState.prototype.terminalDistance=0;mxCellState.prototype.length=0;mxCellState.prototype.segments=null;mxCellState.prototype.shape=null;mxCellState.prototype.text=null;
-mxCellState.prototype.getPerimeterBounds=function(a,b){a=a||0;b=b!=null?b:new mxRectangle(this.x,this.y,this.width,this.height);if(this.shape!=null&&this.shape.stencil!=null){var c=this.shape.stencil.computeAspect(this,b,null);b.x=c.x;b.y=c.y;b.width=this.shape.stencil.w0*c.width;b.height=this.shape.stencil.h0*c.height}a!=0&&b.grow(a);return b};
-mxCellState.prototype.setAbsoluteTerminalPoint=function(a,b){if(b){if(this.absolutePoints==null)this.absolutePoints=[];this.absolutePoints.length==0?this.absolutePoints.push(a):this.absolutePoints[0]=a}else if(this.absolutePoints==null){this.absolutePoints=[];this.absolutePoints.push(null);this.absolutePoints.push(a)}else this.absolutePoints.length==1?this.absolutePoints.push(a):this.absolutePoints[this.absolutePoints.length-1]=a};
-mxCellState.prototype.setCursor=function(a){this.shape!=null&&this.shape.setCursor(a);this.text!=null&&this.text.setCursor(a)};mxCellState.prototype.getVisibleTerminal=function(a){a=this.getVisibleTerminalState(a);return a!=null?a.cell:null};mxCellState.prototype.getVisibleTerminalState=function(a){return a?this.visibleSourceState:this.visibleTargetState};mxCellState.prototype.setVisibleTerminalState=function(a,b){b?this.visibleSourceState=a:this.visibleTargetState=a};
-mxCellState.prototype.destroy=function(){this.view.graph.cellRenderer.destroy(this)};
-mxCellState.prototype.clone=function(){var a=new mxCellState(this.view,this.cell,this.style);if(this.absolutePoints!=null){a.absolutePoints=[];for(var b=0;b<this.absolutePoints.length;b++)a.absolutePoints[b]=this.absolutePoints[b].clone()}if(this.origin!=null)a.origin=this.origin.clone();if(this.absoluteOffset!=null)a.absoluteOffset=this.absoluteOffset.clone();if(this.boundingBox!=null)a.boundingBox=this.boundingBox.clone();a.terminalDistance=this.terminalDistance;a.segments=this.segments;a.length=
-this.length;a.x=this.x;a.y=this.y;a.width=this.width;a.height=this.height;return a};function mxGraphSelectionModel(a){this.graph=a;this.cells=[]}mxGraphSelectionModel.prototype=new mxEventSource;mxGraphSelectionModel.prototype.constructor=mxGraphSelectionModel;mxGraphSelectionModel.prototype.doneResource="none"!=mxClient.language?"done":"";mxGraphSelectionModel.prototype.updatingSelectionResource="none"!=mxClient.language?"updatingSelection":"";mxGraphSelectionModel.prototype.graph=null;
-mxGraphSelectionModel.prototype.singleSelection=!1;mxGraphSelectionModel.prototype.isSingleSelection=function(){return this.singleSelection};mxGraphSelectionModel.prototype.setSingleSelection=function(a){this.singleSelection=a};mxGraphSelectionModel.prototype.isSelected=function(a){return a!=null?mxUtils.indexOf(this.cells,a)>=0:false};mxGraphSelectionModel.prototype.isEmpty=function(){return this.cells.length==0};mxGraphSelectionModel.prototype.clear=function(){this.changeSelection(null,this.cells)};
-mxGraphSelectionModel.prototype.setCell=function(a){a!=null&&this.setCells([a])};mxGraphSelectionModel.prototype.setCells=function(a){if(a!=null){this.singleSelection&&(a=[this.getFirstSelectableCell(a)]);for(var b=[],c=0;c<a.length;c++)this.graph.isCellSelectable(a[c])&&b.push(a[c]);this.changeSelection(b,this.cells)}};mxGraphSelectionModel.prototype.getFirstSelectableCell=function(a){if(a!=null)for(var b=0;b<a.length;b++)if(this.graph.isCellSelectable(a[b]))return a[b];return null};
-mxGraphSelectionModel.prototype.addCell=function(a){a!=null&&this.addCells([a])};mxGraphSelectionModel.prototype.addCells=function(a){if(a!=null){var b=null;if(this.singleSelection){b=this.cells;a=[this.getFirstSelectableCell(a)]}for(var c=[],d=0;d<a.length;d++)!this.isSelected(a[d])&&this.graph.isCellSelectable(a[d])&&c.push(a[d]);this.changeSelection(c,b)}};mxGraphSelectionModel.prototype.removeCell=function(a){a!=null&&this.removeCells([a])};
-mxGraphSelectionModel.prototype.removeCells=function(a){if(a!=null){for(var b=[],c=0;c<a.length;c++)this.isSelected(a[c])&&b.push(a[c]);this.changeSelection(null,b)}};mxGraphSelectionModel.prototype.changeSelection=function(a,b){if(a!=null&&a.length>0&&a[0]!=null||b!=null&&b.length>0&&b[0]!=null){var c=new mxSelectionChange(this,a,b);c.execute();var d=new mxUndoableEdit(this,false);d.add(c);this.fireEvent(new mxEventObject(mxEvent.UNDO,"edit",d))}};
-mxGraphSelectionModel.prototype.cellAdded=function(a){a!=null&&!this.isSelected(a)&&this.cells.push(a)};mxGraphSelectionModel.prototype.cellRemoved=function(a){if(a!=null){a=mxUtils.indexOf(this.cells,a);a>=0&&this.cells.splice(a,1)}};function mxSelectionChange(a,b,c){this.selectionModel=a;this.added=b!=null?b.slice():null;this.removed=c!=null?c.slice():null}
-mxSelectionChange.prototype.execute=function(){var a=mxLog.enter("mxSelectionChange.execute");window.status=mxResources.get(this.selectionModel.updatingSelectionResource)||this.selectionModel.updatingSelectionResource;if(this.removed!=null)for(var b=0;b<this.removed.length;b++)this.selectionModel.cellRemoved(this.removed[b]);if(this.added!=null)for(b=0;b<this.added.length;b++)this.selectionModel.cellAdded(this.added[b]);b=this.added;this.added=this.removed;this.removed=b;window.status=mxResources.get(this.selectionModel.doneResource)||
-this.selectionModel.doneResource;mxLog.leave("mxSelectionChange.execute",a);this.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE,"added",this.added,"removed",this.removed))};function mxCellEditor(a){this.graph=a}mxCellEditor.prototype.graph=null;mxCellEditor.prototype.textarea=null;mxCellEditor.prototype.editingCell=null;mxCellEditor.prototype.trigger=null;mxCellEditor.prototype.modified=!1;mxCellEditor.prototype.emptyLabelText="";mxCellEditor.prototype.textNode="";
-mxCellEditor.prototype.init=function(){this.textarea=document.createElement("textarea");this.textarea.className="mxCellEditor";this.textarea.style.position="absolute";this.textarea.style.overflow="visible";this.textarea.setAttribute("cols","20");this.textarea.setAttribute("rows","4");if(mxClient.IS_GC)this.textarea.style.resize="none";mxEvent.addListener(this.textarea,"blur",mxUtils.bind(this,function(){this.focusLost()}));mxEvent.addListener(this.textarea,"keydown",mxUtils.bind(this,function(a){if(!mxEvent.isConsumed(a))if(a.keyCode==
-113||this.graph.isEnterStopsCellEditing()&&a.keyCode==13&&!mxEvent.isControlDown(a)&&!mxEvent.isShiftDown(a)){this.graph.stopEditing(false);mxEvent.consume(a)}else if(a.keyCode==27){this.graph.stopEditing(true);mxEvent.consume(a)}else{if(this.clearOnChange){this.clearOnChange=false;this.textarea.value=""}this.setModified(true)}}))};mxCellEditor.prototype.isModified=function(){return this.modified};mxCellEditor.prototype.setModified=function(a){this.modified=a};mxCellEditor.prototype.focusLost=function(){this.stopEditing(!this.graph.isInvokesStopCellEditing())};
-mxCellEditor.prototype.startEditing=function(a,b){this.textarea==null&&this.init();this.stopEditing(true);var c=this.graph.getView().getState(a);if(c!=null){this.editingCell=a;this.trigger=b;this.textNode=null;if(c.text!=null&&this.isHideLabel(c)){this.textNode=c.text.node;this.textNode.style.visibility="hidden"}var d=this.graph.getView().scale,d=mxUtils.getValue(c.style,mxConstants.STYLE_FONTSIZE,mxConstants.DEFAULT_FONTSIZE)*d,e=mxUtils.getValue(c.style,mxConstants.STYLE_FONTFAMILY,mxConstants.DEFAULT_FONTFAMILY),
-f=mxUtils.getValue(c.style,mxConstants.STYLE_FONTCOLOR,"black"),g=this.graph.model.isEdge(c.cell)?mxConstants.ALIGN_LEFT:mxUtils.getValue(c.style,mxConstants.STYLE_ALIGN,mxConstants.ALIGN_LEFT),h=(mxUtils.getValue(c.style,mxConstants.STYLE_FONTSTYLE,0)&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD,k=(mxUtils.getValue(c.style,mxConstants.STYLE_FONTSTYLE,0)&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC,i=(mxUtils.getValue(c.style,mxConstants.STYLE_FONTSTYLE,0)&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE;
-this.textarea.style.fontSize=d+"px";this.textarea.style.fontFamily=e;this.textarea.style.textAlign=g;this.textarea.style.color=f;this.textarea.style.fontWeight=h?"bold":"normal";this.textarea.style.fontStyle=k?"italic":"";this.textarea.style.textDecoration=i?"underline":"";d=this.getEditorBounds(c);this.textarea.style.left=d.x+"px";this.textarea.style.top=d.y+"px";this.textarea.style.width=d.width+"px";this.textarea.style.height=d.height+"px";this.textarea.style.zIndex=5;c=this.getInitialValue(c,
-b);if(c==null||c.length==0){c=this.getEmptyLabelText();this.clearOnChange=true}else this.clearOnChange=false;this.setModified(false);this.textarea.value=c;this.graph.container.appendChild(this.textarea);if(this.textarea.style.display!="none"){this.textarea.focus();this.textarea.select()}}};
-mxCellEditor.prototype.stopEditing=function(a){if(this.editingCell!=null){if(this.textNode!=null){this.textNode.style.visibility="visible";this.textNode=null}!a&&this.isModified()&&this.graph.labelChanged(this.editingCell,this.getCurrentValue(),this.trigger);this.trigger=this.editingCell=null;this.textarea.blur();this.textarea.parentNode.removeChild(this.textarea)}};mxCellEditor.prototype.getInitialValue=function(a,b){return this.graph.getEditingValue(a.cell,b)};
-mxCellEditor.prototype.getCurrentValue=function(){return this.textarea.value.replace(/\r/g,"")};mxCellEditor.prototype.isHideLabel=function(){return true};mxCellEditor.prototype.getMinimumSize=function(a){var b=this.graph.getView().scale;return new mxRectangle(0,0,a.text==null?30:a.text.size*b+20,this.textarea.style.textAlign=="left"?120:40)};
-mxCellEditor.prototype.getEditorBounds=function(a){var b=this.graph.getModel().isEdge(a.cell),c=this.graph.getView().scale,d=this.getMinimumSize(a),e=d.width,d=d.height,f=parseInt(a.style[mxConstants.STYLE_SPACING]||2)*c,g=parseInt(a.style[mxConstants.STYLE_SPACING_TOP]||0)*c+f,h=parseInt(a.style[mxConstants.STYLE_SPACING_RIGHT]||0)*c+f,k=parseInt(a.style[mxConstants.STYLE_SPACING_BOTTOM]||0)*c+f,c=parseInt(a.style[mxConstants.STYLE_SPACING_LEFT]||0)*c+f,h=new mxRectangle(a.x,a.y,Math.max(e,a.width-
-c-h),Math.max(d,a.height-g-k));if(b){h.x=a.absoluteOffset.x;h.y=a.absoluteOffset.y;if(a.text!=null&&a.text.boundingBox!=null){if(a.text.boundingBox.x>0)h.x=a.text.boundingBox.x;if(a.text.boundingBox.y>0)h.y=a.text.boundingBox.y}}else if(a.text!=null&&a.text.boundingBox!=null){h.x=Math.min(h.x,a.text.boundingBox.x);h.y=Math.min(h.y,a.text.boundingBox.y)}h.x=h.x+c;h.y=h.y+g;if(a.text!=null&&a.text.boundingBox!=null)if(b){h.width=Math.max(e,a.text.boundingBox.width);h.height=Math.max(d,a.text.boundingBox.height)}else{h.width=
-Math.max(h.width,a.text.boundingBox.width);h.height=Math.max(h.height,a.text.boundingBox.height)}if(this.graph.getModel().isVertex(a.cell)){b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER);if(b==mxConstants.ALIGN_LEFT)h.x=h.x-a.width;else if(b==mxConstants.ALIGN_RIGHT)h.x=h.x+a.width;b=mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants.ALIGN_MIDDLE);if(b==mxConstants.ALIGN_TOP)h.y=h.y-a.height;else if(b==mxConstants.ALIGN_BOTTOM)h.y=
-h.y+a.height}return h};mxCellEditor.prototype.getEmptyLabelText=function(){return this.emptyLabelText};mxCellEditor.prototype.getEditingCell=function(){return this.editingCell};mxCellEditor.prototype.destroy=function(){if(this.textarea!=null){mxEvent.release(this.textarea);this.textarea.parentNode!=null&&this.textarea.parentNode.removeChild(this.textarea);this.textarea=null}};function mxCellRenderer(){this.shapes=mxUtils.clone(this.defaultShapes)}mxCellRenderer.prototype.shapes=null;
-mxCellRenderer.prototype.defaultEdgeShape=mxConnector;mxCellRenderer.prototype.defaultVertexShape=mxRectangleShape;mxCellRenderer.prototype.defaultShapes={};mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ARROW]=mxArrow;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_RECTANGLE]=mxRectangleShape;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ELLIPSE]=mxEllipse;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_DOUBLE_ELLIPSE]=mxDoubleEllipse;
-mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_RHOMBUS]=mxRhombus;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_IMAGE]=mxImageShape;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_LINE]=mxLine;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_LABEL]=mxLabel;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CYLINDER]=mxCylinder;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_SWIMLANE]=mxSwimlane;
-mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CONNECTOR]=mxConnector;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ACTOR]=mxActor;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CLOUD]=mxCloud;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_TRIANGLE]=mxTriangle;mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_HEXAGON]=mxHexagon;mxCellRenderer.prototype.registerShape=function(a,b){this.shapes[a]=b};
-mxCellRenderer.prototype.initialize=function(a,b){var c=a.view.graph.getModel();if(a.view.graph.container!=null&&a.shape==null&&a.cell!=a.view.currentRoot&&(c.isVertex(a.cell)||c.isEdge(a.cell))){this.createShape(a);if(a.shape!=null&&(b==null||b)){this.initializeShape(a);if(a.view.graph.ordered||c.isEdge(a.cell))a.invalidOrder=true;else if(a.view.graph.keepEdgesInForeground&&this.firstEdge!=null)this.firstEdge.parentNode==a.shape.node.parentNode?this.insertState(a,this.firstEdge):this.firstEdge=null;
-a.shape.scale=a.view.scale;this.createCellOverlays(a);this.installListeners(a)}}};mxCellRenderer.prototype.initializeShape=function(a){a.shape.init(a.view.getDrawPane())};mxCellRenderer.prototype.getPreviousStateInContainer=function(a,b){for(var c=null,d=a.view.graph,e=d.getModel(),f=a.cell,g=e.getParent(f);g!=null&&c==null;){c=this.findPreviousStateInContainer(d,g,f,b);f=g;g=e.getParent(f)}return c};
-mxCellRenderer.prototype.findPreviousStateInContainer=function(a,b,c,d){for(var e=null,f=a.getModel(),c=c!=null?b.getIndex(c)-1:f.getChildCount(b)-1;c>=0&&e==null;c--)e=this.findPreviousStateInContainer(a,f.getChildAt(b,c),null,d);if(e==null){e=a.view.getState(b);if(e!=null&&(e.shape==null||e.shape.node==null||e.shape.node.parentNode!=d))e=null}return e};
-mxCellRenderer.prototype.order=function(a){var b=a.shape.node.parentNode,c=this.getPreviousStateInContainer(a,b),d=b.firstChild;if(c!=null){d=c.shape.node;if(c.text!=null&&c.text.node!=null&&c.text.node.parentNode==b)d=c.text.node;d=d.nextSibling}this.insertState(a,d)};
-mxCellRenderer.prototype.orderEdge=function(a){var b=a.view,c=b.graph.getModel();if(b.graph.keepEdgesInForeground){if(this.firstEdge==null||this.firstEdge.parentNode==null||this.firstEdge.parentNode!=a.shape.node.parentNode)this.firstEdge=a.shape.node}else if(b.graph.keepEdgesInBackground){var d=a.shape.node,e=d.parentNode,c=c.getParent(a.cell),b=b.getState(c),e=b!=null&&b.shape!=null&&b.shape.node!=null?b.shape.node.nextSibling:e.firstChild;e!=null&&e!=d&&this.insertState(a,e)}};
-mxCellRenderer.prototype.insertState=function(a,b){a.shape.node.parentNode.insertBefore(a.shape.node,b);a.text!=null&&(a.text.node!=null&&a.text.node.parentNode==a.shape.node.parentNode)&&a.shape.node.parentNode.insertBefore(a.text.node,a.shape.node.nextSibling)};
-mxCellRenderer.prototype.createShape=function(a){if(a.style!=null){var b=mxStencilRegistry.getStencil(a.style[mxConstants.STYLE_SHAPE]);if(b!=null)a.shape=new mxStencilShape(b);else{b=this.getShapeConstructor(a);a.shape=new b}a.shape.points=a.absolutePoints;a.shape.bounds=new mxRectangle(a.x,a.y,a.width,a.height);a.shape.dialect=a.view.graph.dialect;this.configureShape(a)}};
-mxCellRenderer.prototype.getShapeConstructor=function(a){var b=a.style[mxConstants.STYLE_SHAPE],b=b!=null?this.shapes[b]:null;b==null&&(b=a.view.graph.getModel().isEdge(a.cell)?this.defaultEdgeShape:this.defaultVertexShape);return b};
-mxCellRenderer.prototype.configureShape=function(a){a.shape.apply(a);var b=a.view.graph.getImage(a);if(b!=null)a.shape.image=b;var b=a.view.graph.getIndicatorColor(a),c=a.view.graph.getIndicatorShape(a),c=c!=null?this.shapes[c]:null;if(b!=null){a.shape.indicatorShape=c;a.shape.indicatorColor=b;a.shape.indicatorGradientColor=a.view.graph.getIndicatorGradientColor(a);a.shape.indicatorDirection=a.style[mxConstants.STYLE_INDICATOR_DIRECTION]}else{b=a.view.graph.getIndicatorImage(a);if(b!=null)a.shape.indicatorImage=
-b}this.postConfigureShape(a)};mxCellRenderer.prototype.postConfigureShape=function(a){if(a.shape!=null){this.resolveColor(a,"indicatorColor",mxConstants.STYLE_FILLCOLOR);this.resolveColor(a,"indicatorGradientColor",mxConstants.STYLE_GRADIENTCOLOR);this.resolveColor(a,"fill",mxConstants.STYLE_FILLCOLOR);this.resolveColor(a,"stroke",mxConstants.STYLE_STROKECOLOR);this.resolveColor(a,"gradient",mxConstants.STYLE_GRADIENTCOLOR)}};
-mxCellRenderer.prototype.resolveColor=function(a,b,c){var d=a.shape[b],e=a.view.graph,f=null;if(d=="inherit")f=e.model.getParent(a.cell);else if(d=="swimlane"){f=e.model.getTerminal(a.cell,false)!=null?e.model.getTerminal(a.cell,false):a.cell;f=e.getSwimlane(f);c=e.swimlaneIndicatorColorAttribute}else if(d=="indicated")a.shape[b]=a.shape.indicatorColor;if(f!=null){d=e.getView().getState(f);a.shape[b]=null;d!=null&&(a.shape[b]=d.shape!=null&&b!="indicatorColor"?d.shape[b]:d.style[c])}};
-mxCellRenderer.prototype.getLabelValue=function(a){var b=a.view.graph,c=b.getLabel(a.cell);!b.isHtmlLabel(a.cell)&&(!mxUtils.isNode(c)&&b.dialect!=mxConstants.DIALECT_SVG&&c!=null)&&(c=mxUtils.htmlEntities(c,false));return c};
-mxCellRenderer.prototype.createLabel=function(a,b){var c=a.view.graph;c.getModel().isEdge(a.cell);if(a.style[mxConstants.STYLE_FONTSIZE]>0||a.style[mxConstants.STYLE_FONTSIZE]==null){var d=(c.isHtmlLabel(a.cell)||b!=null&&mxUtils.isNode(b))&&c.dialect==mxConstants.DIALECT_SVG;a.text=new mxText(b,new mxRectangle,a.style[mxConstants.STYLE_ALIGN]||mxConstants.ALIGN_CENTER,c.getVerticalAlign(a),a.style[mxConstants.STYLE_FONTCOLOR],a.style[mxConstants.STYLE_FONTFAMILY],a.style[mxConstants.STYLE_FONTSIZE],
-a.style[mxConstants.STYLE_FONTSTYLE],a.style[mxConstants.STYLE_SPACING],a.style[mxConstants.STYLE_SPACING_TOP],a.style[mxConstants.STYLE_SPACING_RIGHT],a.style[mxConstants.STYLE_SPACING_BOTTOM],a.style[mxConstants.STYLE_SPACING_LEFT],a.style[mxConstants.STYLE_HORIZONTAL],a.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],a.style[mxConstants.STYLE_LABEL_BORDERCOLOR],c.isWrapping(a.cell)&&c.isHtmlLabel(a.cell),c.isLabelClipped(a.cell),a.style[mxConstants.STYLE_OVERFLOW],a.style[mxConstants.STYLE_LABEL_PADDING]);
-a.text.opacity=a.style[mxConstants.STYLE_TEXT_OPACITY];a.text.dialect=d?mxConstants.DIALECT_STRICTHTML:a.view.graph.dialect;this.initializeLabel(a);var e=false,f=function(b){var d=a;if(mxClient.IS_TOUCH||e){d=mxEvent.getClientX(b);b=mxEvent.getClientY(b);b=mxUtils.convertPoint(c.container,d,b);d=c.view.getState(c.getCellAt(b.x,b.y))}return d},d=mxClient.IS_TOUCH?"touchmove":"mousemove",g=mxClient.IS_TOUCH?"touchend":"mouseup";mxEvent.addListener(a.text.node,mxClient.IS_TOUCH?"touchstart":"mousedown",
-mxUtils.bind(this,function(b){if(this.isLabelEvent(a,b)){c.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(b,a));e=c.dialect!=mxConstants.DIALECT_SVG&&mxEvent.getSource(b).nodeName=="IMG"}}));mxEvent.addListener(a.text.node,d,mxUtils.bind(this,function(b){this.isLabelEvent(a,b)&&c.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b,f(b)))}));mxEvent.addListener(a.text.node,g,mxUtils.bind(this,function(b){if(this.isLabelEvent(a,b)){c.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(b,f(b)));e=
-false}}));mxEvent.addListener(a.text.node,"dblclick",mxUtils.bind(this,function(b){if(this.isLabelEvent(a,b)){c.dblClick(b,a.cell);mxEvent.consume(b)}}))}};
-mxCellRenderer.prototype.initializeLabel=function(a){var b=a.view.graph;a.text.dialect!=mxConstants.DIALECT_SVG&&(mxClient.IS_SVG&&mxClient.NO_FO?a.text.init(b.container):mxUtils.isVml(a.view.getDrawPane())&&(a.shape.label!=null?a.text.init(a.shape.label):a.text.init(a.shape.node)));if(a.text.node==null){a.text.init(a.view.getDrawPane());a.shape!=null&&a.text!=null&&a.shape.node.parentNode.insertBefore(a.text.node,a.shape.node.nextSibling)}};
-mxCellRenderer.prototype.createCellOverlays=function(a){var b=a.view.graph.getCellOverlays(a.cell),c=null;if(b!=null)for(var c=new mxDictionary,d=0;d<b.length;d++){var e=a.overlays!=null?a.overlays.remove(b[d]):null;if(e==null){e=new mxImageShape(new mxRectangle,b[d].image.src);e.dialect=a.view.graph.dialect;e.preserveImageAspect=false;e.overlay=b[d];this.initializeOverlay(a,e);this.installCellOverlayListeners(a,b[d],e);if(b[d].cursor!=null)e.node.style.cursor=b[d].cursor}c.put(b[d],e)}a.overlays!=
-null&&a.overlays.visit(function(a,b){b.destroy()});a.overlays=c};mxCellRenderer.prototype.initializeOverlay=function(a,b){b.init(a.view.getOverlayPane())};
-mxCellRenderer.prototype.installCellOverlayListeners=function(a,b,c){var d=a.view.graph;mxEvent.addListener(c.node,"click",function(c){d.isEditing()&&d.stopEditing(!d.isInvokesStopCellEditing());b.fireEvent(new mxEventObject(mxEvent.CLICK,"event",c,"cell",a.cell))});var e=mxClient.IS_TOUCH?"touchmove":"mousemove";mxEvent.addListener(c.node,mxClient.IS_TOUCH?"touchstart":"mousedown",function(a){mxEvent.consume(a)});mxEvent.addListener(c.node,e,function(b){d.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b,
-a))});mxClient.IS_TOUCH&&mxEvent.addListener(c.node,"touchend",function(c){b.fireEvent(new mxEventObject(mxEvent.CLICK,"event",c,"cell",a.cell))})};
-mxCellRenderer.prototype.createControl=function(a){var b=a.view.graph,c=b.getFoldingImage(a);if(b.foldingEnabled&&c!=null){if(a.control==null){var d=new mxRectangle(0,0,c.width,c.height);a.control=new mxImageShape(d,c.src);a.control.dialect=b.dialect;a.control.preserveImageAspect=false;this.initControl(a,a.control,true,function(c){if(b.isEnabled()){var d=!b.isCellCollapsed(a.cell);b.foldCells(d,false,[a.cell]);mxEvent.consume(c)}})}}else if(a.control!=null){a.control.destroy();a.control=null}};
-mxCellRenderer.prototype.initControl=function(a,b,c,d){var e=a.view.graph;if(e.isHtmlLabel(a.cell)&&mxClient.NO_FO&&e.dialect==mxConstants.DIALECT_SVG){b.dialect=mxConstants.DIALECT_PREFERHTML;b.init(e.container);b.node.style.zIndex=1}else b.init(a.view.getOverlayPane());b=b.innerNode||b.node;if(d){if(e.isEnabled())b.style.cursor="pointer";mxEvent.addListener(b,"click",d)}if(c){c=mxClient.IS_TOUCH?"touchmove":"mousemove";mxEvent.addListener(b,mxClient.IS_TOUCH?"touchstart":"mousedown",function(b){e.fireMouseEvent(mxEvent.MOUSE_DOWN,
-new mxMouseEvent(b,a));mxEvent.consume(b)});mxEvent.addListener(b,c,function(b){e.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b,a))})}return b};mxCellRenderer.prototype.isShapeEvent=function(){return true};mxCellRenderer.prototype.isLabelEvent=function(){return true};
-mxCellRenderer.prototype.installListeners=function(a){var b=a.view.graph;if(b.dialect==mxConstants.DIALECT_SVG){var c="all";if(b.getModel().isEdge(a.cell)&&a.shape.stroke!=null&&(a.shape.fill==null||a.shape.fill==mxConstants.NONE))c="visibleStroke";a.shape.innerNode!=null?a.shape.innerNode.setAttribute("pointer-events",c):a.shape.node.setAttribute("pointer-events",c)}var d=function(c){var d=a;if(b.dialect!=mxConstants.DIALECT_SVG&&mxEvent.getSource(c).nodeName=="IMG"||mxClient.IS_TOUCH){d=mxEvent.getClientX(c);
-c=mxEvent.getClientY(c);c=mxUtils.convertPoint(b.container,d,c);d=b.view.getState(b.getCellAt(c.x,c.y))}return d},e=false;mxEvent.addListener(a.shape.node,"gesturestart",mxUtils.bind(this,function(a){b.lastTouchTime=0;e=true;mxEvent.consume(a)}));var c=mxClient.IS_TOUCH?"touchmove":"mousemove",f=mxClient.IS_TOUCH?"touchend":"mouseup";mxEvent.addListener(a.shape.node,mxClient.IS_TOUCH?"touchstart":"mousedown",mxUtils.bind(this,function(c){this.isShapeEvent(a,c)&&!e?b.fireMouseEvent(mxEvent.MOUSE_DOWN,
-new mxMouseEvent(c,a.shape!=null&&mxEvent.getSource(c)==a.shape.content?null:a)):e&&mxEvent.consume(c)}));mxEvent.addListener(a.shape.node,c,mxUtils.bind(this,function(c){this.isShapeEvent(a,c)&&!e?b.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(c,a.shape!=null&&mxEvent.getSource(c)==a.shape.content?null:d(c))):e&&mxEvent.consume(c)}));mxEvent.addListener(a.shape.node,f,mxUtils.bind(this,function(c){this.isShapeEvent(a,c)&&!e?b.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(c,a.shape!=null&&
-mxEvent.getSource(c)==a.shape.content?null:d(c))):e&&mxEvent.consume(c)}));var g=mxClient.IS_TOUCH?"gestureend":"dblclick";mxEvent.addListener(a.shape.node,g,mxUtils.bind(this,function(c){e=false;if(g=="gestureend"){b.lastTouchTime=0;if(b.gestureEnabled){b.handleGesture(a,c);mxEvent.consume(c)}}else if(this.isShapeEvent(a,c)){b.dblClick(c,a.shape!=null&&mxEvent.getSource(c)==a.shape.content?null:a.cell);mxEvent.consume(c)}}))};
-mxCellRenderer.prototype.redrawLabel=function(a){var b=this.getLabelValue(a);if(a.text==null&&b!=null&&(mxUtils.isNode(b)||b.length>0))this.createLabel(a,b);else if(a.text!=null&&(b==null||b.length==0)){a.text.destroy();a.text=null}if(a.text!=null){var c=a.view.graph,d=c.isWrapping(a.cell),c=c.isLabelClipped(a.cell),e=this.getLabelBounds(a);if(a.text.value!=b||a.text.isWrapping!=d||a.text.isClipping!=c||a.text.scale!=a.view.scale||!a.text.bounds.equals(e)){a.text.value=b;a.text.bounds=e;a.text.scale=
-this.getTextScale(a);a.text.isWrapping=d;a.text.isClipping=c;a.text.redraw()}}};mxCellRenderer.prototype.getTextScale=function(a){return a.view.scale};
-mxCellRenderer.prototype.getLabelBounds=function(a){var b=a.view.graph,c=b.getModel().isEdge(a.cell),d=new mxRectangle(a.absoluteOffset.x,a.absoluteOffset.y);if(!c){d.x=d.x+a.x;d.y=d.y+a.y;d.width=Math.max(1,a.width);d.height=Math.max(1,a.height);if(b.isSwimlane(a.cell)){c=b.view.scale;a=b.getStartSize(a.cell);if(a.width>0)d.width=a.width*c;else if(a.height>0)d.height=a.height*c}}return d};
-mxCellRenderer.prototype.redrawCellOverlays=function(a){this.createCellOverlays(a);a.overlays!=null&&a.overlays.visit(function(b,c){var d=c.overlay.getBounds(a);if(c.bounds==null||c.scale!=a.view.scale||!c.bounds.equals(d)){c.bounds=d;c.scale=a.view.scale;c.redraw()}})};mxCellRenderer.prototype.redrawControl=function(a){if(a.control!=null){var b=this.getControlBounds(a),c=a.view.scale;if(a.control.scale!=c||!a.control.bounds.equals(b)){a.control.bounds=b;a.control.scale=c;a.control.redraw()}}};
-mxCellRenderer.prototype.getControlBounds=function(a){if(a.control!=null){var b=a.control.scale,c=a.control.bounds.width/b,b=a.control.bounds.height/b,d=a.view.scale;return a.view.graph.getModel().isEdge(a.cell)?new mxRectangle(a.x+a.width/2-c/2*d,a.y+a.height/2-b/2*d,c*d,b*d):new mxRectangle(a.x+c/2*d,a.y+b/2*d,c*d,b*d)}return null};
-mxCellRenderer.prototype.redraw=function(a,b,c){if(a.shape!=null){a.view.graph.getModel().isEdge(a.cell);reconfigure=b!=null?b:false;this.createControl(a);if(a.orderChanged||a.invalidOrder){a.view.graph.ordered?this.order(a):this.orderEdge(a);reconfigure=a.orderChanged}delete a.invalidOrder;delete a.orderChanged;!reconfigure&&!mxUtils.equalEntries(a.shape.style,a.style)&&(reconfigure=true);if(reconfigure){this.configureShape(a);a.shape.reconfigure()}if(b||a.shape.bounds==null||a.shape.scale!=a.view.scale||
-!a.shape.bounds.equals(a)||!mxUtils.equalPoints(a.shape.points,a.absolutePoints)){a.shape.points=a.absolutePoints!=null?a.absolutePoints.slice():null;a.shape.bounds=new mxRectangle(a.x,a.y,a.width,a.height);a.shape.scale=a.view.scale;c==null||c?a.shape.redraw():a.shape.updateBoundingBox()}if(c==null||c){this.redrawLabel(a);this.redrawCellOverlays(a);this.redrawControl(a)}}};
-mxCellRenderer.prototype.destroy=function(a){if(a.shape!=null){if(a.text!=null){a.text.destroy();a.text=null}if(a.overlays!=null){a.overlays.visit(function(a,c){c.destroy()});a.overlays=null}if(a.control!=null){a.control.destroy();a.control=null}a.shape.destroy();a.shape=null}};
-var mxEdgeStyle={EntityRelation:function(a,b,c,d,e){var f=a.view,g=f.graph,d=mxUtils.getValue(a.style,mxConstants.STYLE_SEGMENT,mxConstants.ENTITY_SEGMENT)*f.scale,h=a.absolutePoints,k=h[0],i=h[h.length-1],h=false;if(k!=null){b=new mxCellState;b.x=k.x;b.y=k.y}else if(b!=null){var l=mxUtils.getPortConstraints(b,a,true,mxConstants.DIRECTION_MASK_NONE);if(l!=mxConstants.DIRECTION_MASK_NONE)h=l==mxConstants.DIRECTION_MASK_WEST;else{k=g.getCellGeometry(b.cell);k.relative?h=k.x<=0.5:c!=null&&(h=c.x+c.width<
-b.x)}}else return;k=true;if(i!=null){c=new mxCellState;c.x=i.x;c.y=i.y}else if(c!=null){l=mxUtils.getPortConstraints(c,a,false,mxConstants.DIRECTION_MASK_NONE);if(l!=mxConstants.DIRECTION_MASK_NONE)k=l==mxConstants.DIRECTION_MASK_WEST;else{a=g.getCellGeometry(c.cell);a.relative?k=a.x<=0.5:b!=null&&(k=b.x+b.width<c.x)}}if(b!=null&&c!=null){a=h?b.x:b.x+b.width;b=f.getRoutingCenterY(b);g=k?c.x:c.x+c.width;c=f.getRoutingCenterY(c);f=new mxPoint(a+(h?-d:d),b);i=new mxPoint(g+(k?-d:d),c);if(h==k){d=h?Math.min(a,
-g)-d:Math.max(a,g)+d;e.push(new mxPoint(d,b));e.push(new mxPoint(d,c))}else{if(f.x<i.x==h){d=b+(c-b)/2;e.push(f);e.push(new mxPoint(f.x,d));e.push(new mxPoint(i.x,d))}else e.push(f);e.push(i)}}},Loop:function(a,b,c,d,e){if(b!=null){var c=a.view,f=c.graph,d=d!=null&&d.length>0?d[0]:null;if(d!=null){d=c.transformControlPoint(a,d);mxUtils.contains(b,d.x,d.y)&&(d=null)}var g=0,h=0,k=0,i=0,f=mxUtils.getValue(a.style,mxConstants.STYLE_SEGMENT,f.gridSize)*c.scale,a=mxUtils.getValue(a.style,mxConstants.STYLE_DIRECTION,
-mxConstants.DIRECTION_WEST);if(a==mxConstants.DIRECTION_NORTH||a==mxConstants.DIRECTION_SOUTH){g=c.getRoutingCenterX(b);h=f}else{k=c.getRoutingCenterY(b);i=f}if(d==null||d.x<b.x||d.x>b.x+b.width)if(d!=null){g=d.x;i=Math.max(Math.abs(k-d.y),i)}else a==mxConstants.DIRECTION_NORTH?k=b.y-2*h:a==mxConstants.DIRECTION_SOUTH?k=b.y+b.height+2*h:g=a==mxConstants.DIRECTION_EAST?b.x-2*i:b.x+b.width+2*i;else if(d!=null){g=c.getRoutingCenterX(b);h=Math.max(Math.abs(g-d.x),i);k=d.y;i=0}e.push(new mxPoint(g-h,k-
-i));e.push(new mxPoint(g+h,k+i))}},ElbowConnector:function(a,b,c,d,e){var f=d!=null&&d.length>0?d[0]:null,g=false,h=false;if(b!=null&&c!=null)if(f!=null)var k=Math.min(b.x,c.x),i=Math.max(b.x+b.width,c.x+c.width),h=Math.min(b.y,c.y),l=Math.max(b.y+b.height,c.y+c.height),f=a.view.transformControlPoint(a,f),g=f.y<h||f.y>l,h=f.x<k||f.x>i;else{k=Math.max(b.x,c.x);i=Math.min(b.x+b.width,c.x+c.width);g=k==i;if(!g){h=Math.max(b.y,c.y);l=Math.min(b.y+b.height,c.y+c.height);h=h==l}}!h&&(g||a.style[mxConstants.STYLE_ELBOW]==
-mxConstants.ELBOW_VERTICAL)?mxEdgeStyle.TopToBottom(a,b,c,d,e):mxEdgeStyle.SideToSide(a,b,c,d,e)},SideToSide:function(a,b,c,d,e){var f=a.view,d=d!=null&&d.length>0?d[0]:null,g=a.absolutePoints,h=g[0],g=g[g.length-1];d!=null&&(d=f.transformControlPoint(a,d));if(h!=null){b=new mxCellState;b.x=h.x;b.y=h.y}if(g!=null){c=new mxCellState;c.x=g.x;c.y=g.y}if(b!=null&&c!=null){a=Math.max(b.x,c.x);h=Math.min(b.x+b.width,c.x+c.width);a=d!=null?d.x:h+(a-h)/2;h=f.getRoutingCenterY(b);f=f.getRoutingCenterY(c);
-if(d!=null){if(d.y>=b.y&&d.y<=b.y+b.height)h=d.y;if(d.y>=c.y&&d.y<=c.y+c.height)f=d.y}!mxUtils.contains(c,a,h)&&!mxUtils.contains(b,a,h)&&e.push(new mxPoint(a,h));!mxUtils.contains(c,a,f)&&!mxUtils.contains(b,a,f)&&e.push(new mxPoint(a,f));if(e.length==1)if(d!=null)!mxUtils.contains(c,a,d.y)&&!mxUtils.contains(b,a,d.y)&&e.push(new mxPoint(a,d.y));else{f=Math.max(b.y,c.y);b=Math.min(b.y+b.height,c.y+c.height);e.push(new mxPoint(a,f+(b-f)/2))}}},TopToBottom:function(a,b,c,d,e){var f=a.view,d=d!=null&&
-d.length>0?d[0]:null,g=a.absolutePoints,h=g[0],g=g[g.length-1];d!=null&&(d=f.transformControlPoint(a,d));if(h!=null){b=new mxCellState;b.x=h.x;b.y=h.y}if(g!=null){c=new mxCellState;c.x=g.x;c.y=g.y}if(b!=null&&c!=null){h=Math.max(b.y,c.y);g=Math.min(b.y+b.height,c.y+c.height);a=f.getRoutingCenterX(b);if(d!=null&&d.x>=b.x&&d.x<=b.x+b.width)a=d.x;h=d!=null?d.y:g+(h-g)/2;!mxUtils.contains(c,a,h)&&!mxUtils.contains(b,a,h)&&e.push(new mxPoint(a,h));a=d!=null&&d.x>=c.x&&d.x<=c.x+c.width?d.x:f.getRoutingCenterX(c);
-!mxUtils.contains(c,a,h)&&!mxUtils.contains(b,a,h)&&e.push(new mxPoint(a,h));if(e.length==1)if(d!=null&&e.length==1)!mxUtils.contains(c,d.x,h)&&!mxUtils.contains(b,d.x,h)&&e.push(new mxPoint(d.x,h));else{f=Math.max(b.x,c.x);b=Math.min(b.x+b.width,c.x+c.width);e.push(new mxPoint(f+(b-f)/2,h))}}},SegmentConnector:function(a,b,c,d,e){var f=a.absolutePoints,g=true,h=null,k=f[0];k==null&&b!=null?k=new mxPoint(a.view.getRoutingCenterX(b),a.view.getRoutingCenterY(b)):k!=null&&(k=k.clone());var i=f.length-
-1;if(d!=null&&d.length>0){for(var h=a.view.transformControlPoint(a,d[0]),l=b,m=f[0],n=false,o=false,n=h,p=d.length,q=0;q<2;q++){var t=m!=null&&m.x==n.x,u=m!=null&&m.y==n.y,v=l!=null&&n.y>=l.y&&n.y<=l.y+l.height,l=l!=null&&n.x>=l.x&&n.x<=l.x+l.width,n=u||m==null&&v,o=t||m==null&&l;if(m!=null&&!u&&!t&&(v||l)){g=v?false:true;break}if(o||n){g=n;q==1&&(g=d.length%2==0?n:o);break}l=c;m=f[i];n=a.view.transformControlPoint(a,d[p-1])}g&&(f[0]!=null&&f[0].y!=h.y||f[0]==null&&b!=null&&(h.y<b.y||h.y>b.y+b.height))?
-e.push(new mxPoint(k.x,h.y)):!g&&(f[0]!=null&&f[0].x!=h.x||f[0]==null&&b!=null&&(h.x<b.x||h.x>b.x+b.width))&&e.push(new mxPoint(h.x,k.y));g?k.y=h.y:k.x=h.x;for(q=0;q<d.length;q++){g=!g;h=a.view.transformControlPoint(a,d[q]);g?k.y=h.y:k.x=h.x;e.push(k.clone())}}else{h=k;g=true}k=f[i];k==null&&c!=null&&(k=new mxPoint(a.view.getRoutingCenterX(c),a.view.getRoutingCenterY(c)));g&&(f[i]!=null&&f[i].y!=h.y||f[i]==null&&c!=null&&(h.y<c.y||h.y>c.y+c.height))?e.push(new mxPoint(k.x,h.y)):!g&&(f[i]!=null&&f[i].x!=
-h.x||f[i]==null&&c!=null&&(h.x<c.x||h.x>c.x+c.width))&&e.push(new mxPoint(h.x,k.y));if(f[0]==null&&b!=null)for(;e.length>1&&mxUtils.contains(b,e[1].x,e[1].y);)e=e.splice(1,1);if(f[i]==null&&c!=null)for(;e.length>1&&mxUtils.contains(c,e[e.length-1].x,e[e.length-1].y);)e=e.splice(e.length-1,1)},orthBuffer:10,dirVectors:[[-1,0],[0,-1],[1,0],[0,1],[-1,0],[0,-1],[1,0]],wayPoints1:[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],routePatterns:[[[513,2308,2081,2562],[513,1090,514,
-2184,2114,2561],[513,1090,514,2564,2184,2562],[513,2308,2561,1090,514,2568,2308]],[[514,1057,513,2308,2081,2562],[514,2184,2114,2561],[514,2184,2562,1057,513,2564,2184],[514,1057,513,2568,2308,2561]],[[1090,514,1057,513,2308,2081,2562],[2114,2561],[1090,2562,1057,513,2564,2184],[1090,514,1057,513,2308,2561,2568]],[[2081,2562],[1057,513,1090,514,2184,2114,2561],[1057,513,1090,514,2184,2562,2564],[1057,2561,1090,514,2568,2308]]],inlineRoutePatterns:[[null,[2114,2568],null,null],[null,[514,2081,2114,
-2568],null,null],[null,[2114,2561],null,null],[[2081,2562],[1057,2114,2568],[2184,2562],null]],vertexSeperations:[],limits:[[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0]],LEFT_MASK:32,TOP_MASK:64,RIGHT_MASK:128,BOTTOM_MASK:256,LEFT:1,TOP:2,RIGHT:4,BOTTOM:8,SIDE_MASK:480,CENTER_MASK:512,SOURCE_MASK:1024,TARGET_MASK:2048,VERTEX_MASK:3072,OrthConnector:function(a,b,c,d,e){var f=a.view.graph,g=b==null?false:f.getModel().isEdge(b.cell),f=c==null?false:f.getModel().isEdge(c.cell);if(d!=null&&d.length>0||g||f)mxEdgeStyle.SegmentConnector(a,
-b,c,d,e);else{var d=a.absolutePoints,h=d[0],k=d[d.length-1],d=b!=null?b.x:h.x,g=b!=null?b.y:h.y,i=b!=null?b.width:1,l=b!=null?b.height:1,m=c!=null?c.x:k.x,n=c!=null?c.y:k.y,o=c!=null?c.width:1,p=c!=null?c.height:1,f=a.view.scale*mxEdgeStyle.orthBuffer,q=[mxConstants.DIRECTION_MASK_ALL,mxConstants.DIRECTION_MASK_ALL];b!=null&&(q[0]=mxUtils.getPortConstraints(b,a,true,mxConstants.DIRECTION_MASK_ALL));c!=null&&(q[1]=mxUtils.getPortConstraints(c,a,false,mxConstants.DIRECTION_MASK_ALL));a=[0,0];d=[[d,
-g,i,l],[m,n,o,p]];for(i=0;i<2;i++){mxEdgeStyle.limits[i][1]=d[i][0]-f;mxEdgeStyle.limits[i][2]=d[i][1]-f;mxEdgeStyle.limits[i][4]=d[i][0]+d[i][2]+f;mxEdgeStyle.limits[i][8]=d[i][1]+d[i][3]+f}i=d[0][0]+d[0][2]/2-(d[1][0]+d[1][2]/2);l=d[0][1]+d[0][3]/2-(d[1][1]+d[1][3]/2);g=0;if(i<0)g=l<0?2:1;else if(l<=0){g=3;i==0&&(g=2)}l=null;b!=null&&(l=h);b=[[0.5,0.5],[0.5,0.5]];for(i=0;i<2;i++){if(l!=null){b[i][0]=(l.x-d[i][0])/d[i][2];if(b[i][0]<0.01)a[i]=mxConstants.DIRECTION_MASK_WEST;else if(b[i][0]>0.99)a[i]=
-mxConstants.DIRECTION_MASK_EAST;b[i][1]=(l.y-d[i][1])/d[i][3];if(b[i][1]<0.01)a[i]=mxConstants.DIRECTION_MASK_NORTH;else if(b[i][1]>0.99)a[i]=mxConstants.DIRECTION_MASK_SOUTH}l=null;c!=null&&(l=k)}i=d[0][1]-(d[1][1]+d[1][3]);l=d[0][0]-(d[1][0]+d[1][2]);m=d[1][1]-(d[0][1]+d[0][3]);n=d[1][0]-(d[0][0]+d[0][2]);mxEdgeStyle.vertexSeperations[1]=Math.max(l-2*f,0);mxEdgeStyle.vertexSeperations[2]=Math.max(i-2*f,0);mxEdgeStyle.vertexSeperations[4]=Math.max(m-2*f,0);mxEdgeStyle.vertexSeperations[3]=Math.max(n-
-2*f,0);c=[];h=[];k=[];h[0]=l>=n?mxConstants.DIRECTION_MASK_WEST:mxConstants.DIRECTION_MASK_EAST;k[0]=i>=m?mxConstants.DIRECTION_MASK_NORTH:mxConstants.DIRECTION_MASK_SOUTH;h[1]=mxUtils.reversePortConstraints(h[0]);k[1]=mxUtils.reversePortConstraints(k[0]);l=l>=n?l:n;m=i>=m?i:m;n=[[0,0],[0,0]];o=false;for(i=0;i<2;i++)if(a[i]==0){(h[i]&q[i])==0&&(h[i]=mxUtils.reversePortConstraints(h[i]));(k[i]&q[i])==0&&(k[i]=mxUtils.reversePortConstraints(k[i]));n[i][0]=k[i];n[i][1]=h[i]}if(m>f*2&&l>f*2)if((h[0]&
-q[0])>0&&(k[1]&q[1])>0){n[0][0]=h[0];n[0][1]=k[0];n[1][0]=k[1];n[1][1]=h[1];o=true}else if((k[0]&q[0])>0&&(h[1]&q[1])>0){n[0][0]=k[0];n[0][1]=h[0];n[1][0]=h[1];n[1][1]=k[1];o=true}if(m>f*2&&!o){n[0][0]=k[0];n[0][1]=h[0];n[1][0]=k[1];n[1][1]=h[1];o=true}if(l>f*2&&!o){n[0][0]=h[0];n[0][1]=k[0];n[1][0]=h[1];n[1][1]=k[1]}for(i=0;i<2;i++)if(a[i]==0){(n[i][0]&q[i])==0&&(n[i][0]=n[i][1]);c[i]=n[i][0]&q[i];c[i]=c[i]|(n[i][1]&q[i])<<8;c[i]=c[i]|(n[1-i][i]&q[i])<<16;c[i]=c[i]|(n[1-i][1-i]&q[i])<<24;(c[i]&15)==
-0&&(c[i]=c[i]<<8);(c[i]&3840)==0&&(c[i]=c[i]&15|c[i]>>8);(c[i]&983040)==0&&(c[i]=c[i]&65535|(c[i]&251658240)>>8);a[i]=c[i]&15;if(q[i]==mxConstants.DIRECTION_MASK_WEST||q[i]==mxConstants.DIRECTION_MASK_NORTH||q[i]==mxConstants.DIRECTION_MASK_EAST||q[i]==mxConstants.DIRECTION_MASK_SOUTH)a[i]=q[i]}i=a[0]==mxConstants.DIRECTION_MASK_EAST?3:a[0];q=a[1]==mxConstants.DIRECTION_MASK_EAST?3:a[1];i=i-g;q=q-g;i<1&&(i=i+4);q<1&&(q=q+4);q=mxEdgeStyle.routePatterns[i-1][q-1];mxEdgeStyle.wayPoints1[0][0]=d[0][0];
-mxEdgeStyle.wayPoints1[0][1]=d[0][1];switch(a[0]){case mxConstants.DIRECTION_MASK_WEST:mxEdgeStyle.wayPoints1[0][0]=mxEdgeStyle.wayPoints1[0][0]-f;mxEdgeStyle.wayPoints1[0][1]=mxEdgeStyle.wayPoints1[0][1]+b[0][1]*d[0][3];break;case mxConstants.DIRECTION_MASK_SOUTH:mxEdgeStyle.wayPoints1[0][0]=mxEdgeStyle.wayPoints1[0][0]+b[0][0]*d[0][2];mxEdgeStyle.wayPoints1[0][1]=mxEdgeStyle.wayPoints1[0][1]+(d[0][3]+f);break;case mxConstants.DIRECTION_MASK_EAST:mxEdgeStyle.wayPoints1[0][0]=mxEdgeStyle.wayPoints1[0][0]+
-(d[0][2]+f);mxEdgeStyle.wayPoints1[0][1]=mxEdgeStyle.wayPoints1[0][1]+b[0][1]*d[0][3];break;case mxConstants.DIRECTION_MASK_NORTH:mxEdgeStyle.wayPoints1[0][0]=mxEdgeStyle.wayPoints1[0][0]+b[0][0]*d[0][2];mxEdgeStyle.wayPoints1[0][1]=mxEdgeStyle.wayPoints1[0][1]-f}f=0;h=c=(a[0]&(mxConstants.DIRECTION_MASK_EAST|mxConstants.DIRECTION_MASK_WEST))>0?0:1;for(i=k=0;i<q.length;i++){k=q[i]&15;p=k==mxConstants.DIRECTION_MASK_EAST?3:k;p=p+g;p>4&&(p=p-4);l=mxEdgeStyle.dirVectors[p-1];k=p%2>0?0:1;if(k!=c){f++;
-mxEdgeStyle.wayPoints1[f][0]=mxEdgeStyle.wayPoints1[f-1][0];mxEdgeStyle.wayPoints1[f][1]=mxEdgeStyle.wayPoints1[f-1][1]}var t=(q[i]&mxEdgeStyle.TARGET_MASK)>0,o=(q[i]&mxEdgeStyle.SOURCE_MASK)>0,m=(q[i]&mxEdgeStyle.SIDE_MASK)>>5,m=m<<g;m>15&&(m=m>>4);n=(q[i]&mxEdgeStyle.CENTER_MASK)>0;if((o||t)&&m<9){p=0;o=o?0:1;p=n&&k==0?d[o][0]+b[o][0]*d[o][2]:n?d[o][1]+b[o][1]*d[o][3]:mxEdgeStyle.limits[o][m];if(k==0){m=(p-mxEdgeStyle.wayPoints1[f][0])*l[0];m>0&&(mxEdgeStyle.wayPoints1[f][0]=mxEdgeStyle.wayPoints1[f][0]+
-l[0]*m)}else{m=(p-mxEdgeStyle.wayPoints1[f][1])*l[1];m>0&&(mxEdgeStyle.wayPoints1[f][1]=mxEdgeStyle.wayPoints1[f][1]+l[1]*m)}}else if(n){mxEdgeStyle.wayPoints1[f][0]=mxEdgeStyle.wayPoints1[f][0]+l[0]*Math.abs(mxEdgeStyle.vertexSeperations[p]/2);mxEdgeStyle.wayPoints1[f][1]=mxEdgeStyle.wayPoints1[f][1]+l[1]*Math.abs(mxEdgeStyle.vertexSeperations[p]/2)}f>0&&mxEdgeStyle.wayPoints1[f][k]==mxEdgeStyle.wayPoints1[f-1][k]?f--:c=k}for(i=0;i<=f;i++){if(i==f&&(((a[1]&(mxConstants.DIRECTION_MASK_EAST|mxConstants.DIRECTION_MASK_WEST))>
-0?0:1)==h?0:1)!=(f+1)%2)break;e.push(new mxPoint(mxEdgeStyle.wayPoints1[i][0],mxEdgeStyle.wayPoints1[i][1]))}}},getRoutePattern:function(a,b,c,d){var e=a[0]==mxConstants.DIRECTION_MASK_EAST?3:a[0],a=a[1]==mxConstants.DIRECTION_MASK_EAST?3:a[1],e=e-b,a=a-b;e<1&&(e=e+4);a<1&&(a=a+4);b=routePatterns[e-1][a-1];if(c==0||d==0)inlineRoutePatterns[e-1][a-1]!=null&&(b=inlineRoutePatterns[e-1][a-1]);return b}},mxStyleRegistry={values:[],putValue:function(a,b){mxStyleRegistry.values[a]=b},getValue:function(a){return mxStyleRegistry.values[a]},
-getName:function(a){for(var b in mxStyleRegistry.values)if(mxStyleRegistry.values[b]==a)return b;return null}};mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW,mxEdgeStyle.ElbowConnector);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION,mxEdgeStyle.EntityRelation);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP,mxEdgeStyle.Loop);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE,mxEdgeStyle.SideToSide);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM,mxEdgeStyle.TopToBottom);
-mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL,mxEdgeStyle.OrthConnector);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT,mxEdgeStyle.SegmentConnector);mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE,mxPerimeter.EllipsePerimeter);mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE,mxPerimeter.RectanglePerimeter);mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS,mxPerimeter.RhombusPerimeter);mxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE,mxPerimeter.TrianglePerimeter);
-function mxGraphView(a){this.graph=a;this.translate=new mxPoint;this.graphBounds=new mxRectangle;this.states=new mxDictionary}mxGraphView.prototype=new mxEventSource;mxGraphView.prototype.constructor=mxGraphView;mxGraphView.prototype.EMPTY_POINT=new mxPoint;mxGraphView.prototype.doneResource="none"!=mxClient.language?"done":"";mxGraphView.prototype.updatingDocumentResource="none"!=mxClient.language?"updatingDocument":"";mxGraphView.prototype.allowEval=!1;
-mxGraphView.prototype.captureDocumentGesture=!0;mxGraphView.prototype.rendering=!0;mxGraphView.prototype.graph=null;mxGraphView.prototype.currentRoot=null;mxGraphView.prototype.graphBounds=null;mxGraphView.prototype.scale=1;mxGraphView.prototype.translate=null;mxGraphView.prototype.updateStyle=!1;mxGraphView.prototype.getGraphBounds=function(){return this.graphBounds};mxGraphView.prototype.setGraphBounds=function(a){this.graphBounds=a};
-mxGraphView.prototype.getBounds=function(a){var b=null;if(a!=null&&a.length>0)for(var c=this.graph.getModel(),d=0;d<a.length;d++)if(c.isVertex(a[d])||c.isEdge(a[d])){var e=this.getState(a[d]);e!=null&&(b==null?b=new mxRectangle(e.x,e.y,e.width,e.height):b.add(e))}return b};mxGraphView.prototype.setCurrentRoot=function(a){if(this.currentRoot!=a){var b=new mxCurrentRootChange(this,a);b.execute();var c=new mxUndoableEdit(this,false);c.add(b);this.fireEvent(new mxEventObject(mxEvent.UNDO,"edit",c));this.graph.sizeDidChange()}return a};
-mxGraphView.prototype.scaleAndTranslate=function(a,b,c){var d=this.scale,e=new mxPoint(this.translate.x,this.translate.y);if(this.scale!=a||this.translate.x!=b||this.translate.y!=c){this.scale=a;this.translate.x=b;this.translate.y=c;if(this.isEventsEnabled()){this.revalidate();this.graph.sizeDidChange()}}this.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE,"scale",a,"previousScale",d,"translate",this.translate,"previousTranslate",e))};mxGraphView.prototype.getScale=function(){return this.scale};
-mxGraphView.prototype.setScale=function(a){var b=this.scale;if(this.scale!=a){this.scale=a;if(this.isEventsEnabled()){this.revalidate();this.graph.sizeDidChange()}}this.fireEvent(new mxEventObject(mxEvent.SCALE,"scale",a,"previousScale",b))};mxGraphView.prototype.getTranslate=function(){return this.translate};
-mxGraphView.prototype.setTranslate=function(a,b){var c=new mxPoint(this.translate.x,this.translate.y);if(this.translate.x!=a||this.translate.y!=b){this.translate.x=a;this.translate.y=b;if(this.isEventsEnabled()){this.revalidate();this.graph.sizeDidChange()}}this.fireEvent(new mxEventObject(mxEvent.TRANSLATE,"translate",this.translate,"previousTranslate",c))};mxGraphView.prototype.refresh=function(){this.currentRoot!=null&&this.clear();this.revalidate()};
-mxGraphView.prototype.revalidate=function(){this.invalidate();this.validate()};mxGraphView.prototype.clear=function(a,b,c){var d=this.graph.getModel(),a=a||d.getRoot(),b=b!=null?b:false,c=c!=null?c:true;this.removeState(a);if(c&&(b||a!=this.currentRoot))for(var c=d.getChildCount(a),e=0;e<c;e++)this.clear(d.getChildAt(a,e),b);else this.invalidate(a)};
-mxGraphView.prototype.invalidate=function(a,b,c,d){var e=this.graph.getModel(),a=a||e.getRoot(),b=b!=null?b:true,c=c!=null?c:true,d=d!=null?d:false,f=this.getState(a);if(f!=null){f.invalid=true;if(d)f.orderChanged=true}if(b)for(var g=e.getChildCount(a),f=0;f<g;f++)this.invalidate(e.getChildAt(a,f),b,c,d);if(c){d=e.getEdgeCount(a);for(f=0;f<d;f++)this.invalidate(e.getEdgeAt(a,f),b,c)}};
-mxGraphView.prototype.validate=function(a){var b=mxLog.enter("mxGraphView.validate");window.status=mxResources.get(this.updatingDocumentResource)||this.updatingDocumentResource;a=a||(this.currentRoot!=null?this.currentRoot:this.graph.getModel().getRoot());this.validateBounds(null,a);a=this.validatePoints(null,a);a==null&&(a=new mxRectangle);this.setGraphBounds(a);this.validateBackground();window.status=mxResources.get(this.doneResource)||this.doneResource;mxLog.leave("mxGraphView.validate",b)};
-mxGraphView.prototype.createBackgroundPageShape=function(a){return new mxRectangleShape(a,"white","black")};
-mxGraphView.prototype.validateBackground=function(){var a=this.graph.getBackgroundImage();if(a!=null){if(this.backgroundImage==null||this.backgroundImage.image!=a.src){this.backgroundImage!=null&&this.backgroundImage.destroy();var b=new mxRectangle(0,0,1,1);this.backgroundImage=new mxImageShape(b,a.src);this.backgroundImage.dialect=this.graph.dialect;this.backgroundImage.init(this.backgroundPane);this.backgroundImage.redraw()}this.redrawBackgroundImage(this.backgroundImage,a)}else if(this.backgroundImage!=
-null){this.backgroundImage.destroy();this.backgroundImage=null}if(this.graph.pageVisible){b=this.getBackgroundPageBounds();if(this.backgroundPageShape==null){this.backgroundPageShape=this.createBackgroundPageShape(b);this.backgroundPageShape.scale=this.scale;this.backgroundPageShape.isShadow=true;this.backgroundPageShape.dialect=this.graph.dialect;this.backgroundPageShape.init(this.backgroundPane);this.backgroundPageShape.redraw();mxEvent.addListener(this.backgroundPageShape.node,"dblclick",mxUtils.bind(this,
-function(a){this.graph.dblClick(a)}));a=mxClient.IS_TOUCH?"touchmove":"mousemove";b=mxClient.IS_TOUCH?"touchend":"mouseup";mxEvent.addListener(this.backgroundPageShape.node,mxClient.IS_TOUCH?"touchstart":"mousedown",mxUtils.bind(this,function(a){this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(a))}));mxEvent.addListener(this.backgroundPageShape.node,a,mxUtils.bind(this,function(a){this.graph.tooltipHandler!=null&&this.graph.tooltipHandler.isHideOnHover()&&this.graph.tooltipHandler.hide();
-this.graph.isMouseDown&&!mxEvent.isConsumed(a)&&this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(a))}));mxEvent.addListener(this.backgroundPageShape.node,b,mxUtils.bind(this,function(a){this.graph.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(a))}))}else{this.backgroundPageShape.scale=this.scale;this.backgroundPageShape.bounds=b;this.backgroundPageShape.redraw()}}else if(this.backgroundPageShape!=null){this.backgroundPageShape.destroy();this.backgroundPageShape=null}};
-mxGraphView.prototype.getBackgroundPageBounds=function(){var a=this.graph.pageFormat,b=this.scale*this.graph.pageScale;return new mxRectangle(this.scale*this.translate.x,this.scale*this.translate.y,a.width*b,a.height*b)};mxGraphView.prototype.redrawBackgroundImage=function(a,b){a.scale=this.scale;a.bounds.x=this.scale*this.translate.x;a.bounds.y=this.scale*this.translate.y;a.bounds.width=this.scale*b.width;a.bounds.height=this.scale*b.height;a.redraw()};
-mxGraphView.prototype.validateBounds=function(a,b){var c=this.graph.getModel(),d=this.getState(b,true);if(d!=null&&d.invalid){if(this.graph.isCellVisible(b)){if(b!=this.currentRoot&&a!=null){d.absoluteOffset.x=0;d.absoluteOffset.y=0;d.origin.x=a.origin.x;d.origin.y=a.origin.y;var e=this.graph.getCellGeometry(b);if(e!=null){if(!c.isEdge(b)){var f=e.offset||this.EMPTY_POINT;if(e.relative){d.origin.x=d.origin.x+(e.x*a.width/this.scale+f.x);d.origin.y=d.origin.y+(e.y*a.height/this.scale+f.y)}else{d.absoluteOffset.x=
-this.scale*f.x;d.absoluteOffset.y=this.scale*f.y;d.origin.x=d.origin.x+e.x;d.origin.y=d.origin.y+e.y}}d.x=this.scale*(this.translate.x+d.origin.x);d.y=this.scale*(this.translate.y+d.origin.y);d.width=this.scale*e.width;d.height=this.scale*e.height;c.isVertex(b)&&this.updateVertexLabelOffset(d)}}}else this.removeState(b);f=this.graph.getChildOffsetForCell(b);if(f!=null){d.origin.x=d.origin.x+f.x;d.origin.y=d.origin.y+f.y}}if(d!=null&&(!this.graph.isCellCollapsed(b)||b==this.currentRoot)){e=c.getChildCount(b);
-for(f=0;f<e;f++){var g=c.getChildAt(b,f);this.validateBounds(d,g)}}};
-mxGraphView.prototype.updateVertexLabelOffset=function(a){var b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER);if(b==mxConstants.ALIGN_LEFT)a.absoluteOffset.x=a.absoluteOffset.x-a.width;else if(b==mxConstants.ALIGN_RIGHT)a.absoluteOffset.x=a.absoluteOffset.x+a.width;b=mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants.ALIGN_MIDDLE);if(b==mxConstants.ALIGN_TOP)a.absoluteOffset.y=a.absoluteOffset.y-a.height;else if(b==mxConstants.ALIGN_BOTTOM)a.absoluteOffset.y=
-a.absoluteOffset.y+a.height};
-mxGraphView.prototype.validatePoints=function(a,b){var c=this.graph.getModel(),d=this.getState(b),e=null;if(d!=null){if(d.invalid){var f=this.graph.getCellGeometry(b);if(f!=null&&c.isEdge(b)){var g=this.getState(this.getVisibleTerminal(b,true));d.setVisibleTerminalState(g,true);if(g!=null&&c.isEdge(g.cell)&&!c.isAncestor(g.cell,b)){var h=this.getState(c.getParent(g.cell));this.validatePoints(h,g.cell)}var k=this.getState(this.getVisibleTerminal(b,false));d.setVisibleTerminalState(k,false);if(k!=null&&
-c.isEdge(k.cell)&&!c.isAncestor(k.cell,b)){h=this.getState(c.getParent(k.cell));this.validatePoints(h,k.cell)}this.updateFixedTerminalPoints(d,g,k);this.updatePoints(d,f.points,g,k);this.updateFloatingTerminalPoints(d,g,k);this.updateEdgeBounds(d);this.updateEdgeLabelOffset(d)}else if(f!=null&&f.relative&&a!=null&&c.isEdge(a.cell)){f=this.getPoint(a,f);if(f!=null){d.x=f.x;d.y=f.y;f.x=f.x/this.scale-this.translate.x;f.y=f.y/this.scale-this.translate.y;d.origin=f;this.childMoved(a,d)}}d.invalid=false;
-b!=this.currentRoot&&this.graph.cellRenderer.redraw(d,false,this.isRendering())}if(c.isEdge(b)||c.isVertex(b)){d.shape!=null&&d.shape.boundingBox!=null&&(e=d.shape.boundingBox.clone());d.text!=null&&!this.graph.isLabelClipped(d.cell)&&d.text.boundingBox!=null&&(e!=null?e.add(d.text.boundingBox):e=d.text.boundingBox.clone())}}if(d!=null&&(!this.graph.isCellCollapsed(b)||b==this.currentRoot)){f=c.getChildCount(b);for(g=0;g<f;g++){h=c.getChildAt(b,g);h=this.validatePoints(d,h);h!=null&&(e==null?e=h:
-e.add(h))}}return e};mxGraphView.prototype.childMoved=function(a,b){var c=b.cell;if(!this.graph.isCellCollapsed(c)||c==this.currentRoot)for(var d=this.graph.getModel(),e=d.getChildCount(c),f=0;f<e;f++)this.validateBounds(b,d.getChildAt(c,f))};mxGraphView.prototype.updateFixedTerminalPoints=function(a,b,c){this.updateFixedTerminalPoint(a,b,true,this.graph.getConnectionConstraint(a,b,true));this.updateFixedTerminalPoint(a,c,false,this.graph.getConnectionConstraint(a,c,false))};
-mxGraphView.prototype.updateFixedTerminalPoint=function(a,b,c,d){var e=null;d!=null&&(e=this.graph.getConnectionPoint(b,d));if(e==null&&b==null){var b=this.scale,d=this.translate,f=a.origin,e=this.graph.getCellGeometry(a.cell).getTerminalPoint(c);e!=null&&(e=new mxPoint(b*(d.x+e.x+f.x),b*(d.y+e.y+f.y)))}a.setAbsoluteTerminalPoint(e,c)};
-mxGraphView.prototype.updatePoints=function(a,b,c,d){if(a!=null){var e=[];e.push(a.absolutePoints[0]);var f=this.getEdgeStyle(a,b,c,d);if(f!=null){c=this.getTerminalPort(a,c,true);d=this.getTerminalPort(a,d,false);f(a,c,d,b,e)}else if(b!=null)for(f=0;f<b.length;f++)if(b[f]!=null){d=mxUtils.clone(b[f]);e.push(this.transformControlPoint(a,d))}b=a.absolutePoints;e.push(b[b.length-1]);a.absolutePoints=e}};
-mxGraphView.prototype.transformControlPoint=function(a,b){var c=a.origin;return new mxPoint(this.scale*(b.x+this.translate.x+c.x),this.scale*(b.y+this.translate.y+c.y))};
-mxGraphView.prototype.getEdgeStyle=function(a,b,c,d){a=c!=null&&c==d?mxUtils.getValue(a.style,mxConstants.STYLE_LOOP,this.graph.defaultLoopStyle):!mxUtils.getValue(a.style,mxConstants.STYLE_NOEDGESTYLE,false)?a.style[mxConstants.STYLE_EDGE]:null;if(typeof a=="string"){b=mxStyleRegistry.getValue(a);b==null&&this.isAllowEval()&&(b=mxUtils.eval(a));a=b}return typeof a=="function"?a:null};
-mxGraphView.prototype.updateFloatingTerminalPoints=function(a,b,c){var d=a.absolutePoints,e=d[0];d[d.length-1]==null&&c!=null&&this.updateFloatingTerminalPoint(a,c,b,false);e==null&&b!=null&&this.updateFloatingTerminalPoint(a,b,c,true)};
-mxGraphView.prototype.updateFloatingTerminalPoint=function(a,b,c,d){var b=this.getTerminalPort(a,b,d),e=this.getNextPoint(a,c,d),c=mxUtils.toRadians(Number(b.style[mxConstants.STYLE_ROTATION]||"0")),f=new mxPoint(b.getCenterX(),b.getCenterY());if(c!=0)var g=Math.cos(-c),h=Math.sin(-c),e=mxUtils.getRotatedPoint(e,g,h,f);g=parseFloat(a.style[mxConstants.STYLE_PERIMETER_SPACING]||0);g=g+parseFloat(a.style[d?mxConstants.STYLE_SOURCE_PERIMETER_SPACING:mxConstants.STYLE_TARGET_PERIMETER_SPACING]||0);b=
-this.getPerimeterPoint(b,e,this.graph.isOrthogonal(a),g);if(c!=0){g=Math.cos(c);h=Math.sin(c);b=mxUtils.getRotatedPoint(b,g,h,f)}a.setAbsoluteTerminalPoint(b,d)};mxGraphView.prototype.getTerminalPort=function(a,b,c){a=mxUtils.getValue(a.style,c?mxConstants.STYLE_SOURCE_PORT:mxConstants.STYLE_TARGET_PORT);if(a!=null){a=this.getState(this.graph.getModel().getCell(a));a!=null&&(b=a)}return b};
-mxGraphView.prototype.getPerimeterPoint=function(a,b,c,d){var e=null;if(a!=null){var f=this.getPerimeterFunction(a);if(f!=null&&b!=null){d=this.getPerimeterBounds(a,d);if(d.width>0||d.height>0)e=f(d,a,b,c)}e==null&&(e=this.getPoint(a))}return e};mxGraphView.prototype.getRoutingCenterX=function(a){var b=a.style!=null?parseFloat(a.style[mxConstants.STYLE_ROUTING_CENTER_X])||0:0;return a.getCenterX()+b*a.width};
-mxGraphView.prototype.getRoutingCenterY=function(a){var b=a.style!=null?parseFloat(a.style[mxConstants.STYLE_ROUTING_CENTER_Y])||0:0;return a.getCenterY()+b*a.height};mxGraphView.prototype.getPerimeterBounds=function(a,b){b=b!=null?b:0;a!=null&&(b=b+parseFloat(a.style[mxConstants.STYLE_PERIMETER_SPACING]||0));return a.getPerimeterBounds(b*this.scale)};
-mxGraphView.prototype.getPerimeterFunction=function(a){a=a.style[mxConstants.STYLE_PERIMETER];if(typeof a=="string"){var b=mxStyleRegistry.getValue(a);b==null&&this.isAllowEval()&&(b=mxUtils.eval(a));a=b}return typeof a=="function"?a:null};mxGraphView.prototype.getNextPoint=function(a,b,c){var a=a.absolutePoints,d=null;if(a!=null&&(c||a.length>2||b==null)){d=a.length;d=a[c?Math.min(1,d-1):Math.max(0,d-2)]}d==null&&b!=null&&(d=new mxPoint(b.getCenterX(),b.getCenterY()));return d};
-mxGraphView.prototype.getVisibleTerminal=function(a,b){for(var c=this.graph.getModel(),d=c.getTerminal(a,b),e=d;d!=null&&d!=this.currentRoot;){if(!this.graph.isCellVisible(e)||this.graph.isCellCollapsed(d))e=d;d=c.getParent(d)}c.getParent(e)==c.getRoot()&&(e=null);return e};
-mxGraphView.prototype.updateEdgeBounds=function(a){var b=a.absolutePoints;a.length=0;if(b!=null&&b.length>0){var c=b[0],d=b[b.length-1];if(c==null||d==null)a.cell!=this.currentRoot&&this.clear(a.cell,true);else{if(c.x!=d.x||c.y!=d.y){var e=d.x-c.x,f=d.y-c.y;a.terminalDistance=Math.sqrt(e*e+f*f)}else a.terminalDistance=0;var d=0,g=[],f=c;if(f!=null){for(var c=f.x,h=f.y,k=c,i=h,l=1;l<b.length;l++){var m=b[l];if(m!=null){e=f.x-m.x;f=f.y-m.y;e=Math.sqrt(e*e+f*f);g.push(e);d=d+e;f=m;c=Math.min(f.x,c);
-h=Math.min(f.y,h);k=Math.max(f.x,k);i=Math.max(f.y,i)}}a.length=d;a.segments=g;a.x=c;a.y=h;a.width=Math.max(1,k-c);a.height=Math.max(1,i-h)}}}};
-mxGraphView.prototype.getPoint=function(a,b){var c=a.getCenterX(),d=a.getCenterY();if(a.segments!=null&&(b==null||b.relative)){for(var e=a.absolutePoints.length,f=((b!=null?b.x/2:0)+0.5)*a.length,g=a.segments[0],h=0,k=1;f>h+g&&k<e-1;){h=h+g;g=a.segments[k++]}e=g==0?0:(f-h)/g;f=a.absolutePoints[k-1];k=a.absolutePoints[k];if(f!=null&&k!=null){h=c=d=0;if(b!=null){var d=b.y,i=b.offset;if(i!=null){c=i.x;h=i.y}}i=k.x-f.x;k=k.y-f.y;c=f.x+i*e+((g==0?0:k/g)*d+c)*this.scale;d=f.y+k*e-((g==0?0:i/g)*d-h)*this.scale}}else if(b!=
-null){i=b.offset;if(i!=null){c=c+i.x;d=d+i.y}}return new mxPoint(c,d)};
-mxGraphView.prototype.getRelativePoint=function(a,b,c){var d=this.graph.getModel().getGeometry(a.cell);if(d!=null){var e=a.absolutePoints.length;if(d.relative&&e>1){for(var d=a.length,f=a.segments,g=a.absolutePoints[0],h=a.absolutePoints[1],k=mxUtils.ptSegDistSq(g.x,g.y,h.x,h.y,b,c),i=0,l=0,m=0,n=2;n<e;n++){l=l+f[n-2];h=a.absolutePoints[n];g=mxUtils.ptSegDistSq(g.x,g.y,h.x,h.y,b,c);if(g<=k){k=g;i=n-1;m=l}g=h}e=f[i];g=a.absolutePoints[i];h=a.absolutePoints[i+1];f=h.x;k=h.y;a=g.x-f;i=g.y-k;f=(a-(b-
-f))*a+(i-(c-k))*i;a=Math.sqrt(f<=0?0:f*f/(a*a+i*i));a>e&&(a=e);e=Math.sqrt(mxUtils.ptSegDistSq(g.x,g.y,h.x,h.y,b,c));mxUtils.relativeCcw(g.x,g.y,h.x,h.y,b,c)==-1&&(e=-e);return new mxPoint((d/2-m-a)/d*-2,e/this.scale)}}return new mxPoint};
-mxGraphView.prototype.updateEdgeLabelOffset=function(a){var b=a.absolutePoints;a.absoluteOffset.x=a.getCenterX();a.absoluteOffset.y=a.getCenterY();if(b!=null&&b.length>0&&a.segments!=null){var c=this.graph.getCellGeometry(a.cell);if(c.relative){var d=this.getPoint(a,c);if(d!=null)a.absoluteOffset=d}else{var d=b[0],e=b[b.length-1];if(d!=null&&e!=null){var b=e.x-d.x,f=e.y-d.y,g=e=0,c=c.offset;if(c!=null){e=c.x;g=c.y}c=d.y+f/2+g*this.scale;a.absoluteOffset.x=d.x+b/2+e*this.scale;a.absoluteOffset.y=c}}}};
-mxGraphView.prototype.getState=function(a,b){var b=b||false,c=null;if(a!=null){c=this.states.get(a);if(this.graph.isCellVisible(a))if(c==null&&b&&this.graph.isCellVisible(a)){c=this.createState(a);this.states.put(a,c)}else if(b&&c!=null&&this.updateStyle)c.style=this.graph.getCellStyle(a)}return c};mxGraphView.prototype.isRendering=function(){return this.rendering};mxGraphView.prototype.setRendering=function(a){this.rendering=a};mxGraphView.prototype.isAllowEval=function(){return this.allowEval};
-mxGraphView.prototype.setAllowEval=function(a){this.allowEval=a};mxGraphView.prototype.getStates=function(){return this.states};mxGraphView.prototype.setStates=function(a){this.states=a};mxGraphView.prototype.getCellStates=function(a){if(a==null)return this.states;for(var b=[],c=0;c<a.length;c++){var d=this.getState(a[c]);d!=null&&b.push(d)}return b};mxGraphView.prototype.removeState=function(a){var b=null;if(a!=null){b=this.states.remove(a);if(b!=null){this.graph.cellRenderer.destroy(b);b.destroy()}}return b};
-mxGraphView.prototype.createState=function(a){var b=this.graph.getCellStyle(a),a=new mxCellState(this,a,b);this.graph.cellRenderer.initialize(a,this.isRendering());return a};mxGraphView.prototype.getCanvas=function(){return this.canvas};mxGraphView.prototype.getBackgroundPane=function(){return this.backgroundPane};mxGraphView.prototype.getDrawPane=function(){return this.drawPane};mxGraphView.prototype.getOverlayPane=function(){return this.overlayPane};
-mxGraphView.prototype.isContainerEvent=function(a){a=mxEvent.getSource(a);return a==this.graph.container||a.parentNode==this.backgroundPane||a.parentNode!=null&&a.parentNode.parentNode==this.backgroundPane||a==this.canvas.parentNode||a==this.canvas||a==this.backgroundPane||a==this.drawPane||a==this.overlayPane};
-mxGraphView.prototype.isScrollEvent=function(a){var b=mxUtils.getOffset(this.graph.container),a=new mxPoint(a.clientX-b.x,a.clientY-b.y),b=this.graph.container.offsetWidth,c=this.graph.container.clientWidth;if(b>c&&a.x>c+2&&a.x<=b)return true;b=this.graph.container.offsetHeight;c=this.graph.container.clientHeight;return b>c&&a.y>c+2&&a.y<=b?true:false};
-mxGraphView.prototype.init=function(){this.installListeners();var a=this.graph;a.dialect==mxConstants.DIALECT_SVG?this.createSvg():a.dialect==mxConstants.DIALECT_VML?this.createVml():this.createHtml()};
-mxGraphView.prototype.installListeners=function(){var a=this.graph,b=a.container;if(b!=null){var c=mxClient.IS_TOUCH?"touchmove":"mousemove",d=mxClient.IS_TOUCH?"touchend":"mouseup";mxEvent.addListener(b,mxClient.IS_TOUCH?"touchstart":"mousedown",mxUtils.bind(this,function(b){mxClient.IS_TOUCH&&a.isEditing()&&a.stopEditing(!a.isInvokesStopCellEditing());this.isContainerEvent(b)&&(!mxClient.IS_IE&&!mxClient.IS_GC&&!mxClient.IS_OP&&!mxClient.IS_SF||!this.isScrollEvent(b))&&a.fireMouseEvent(mxEvent.MOUSE_DOWN,
-new mxMouseEvent(b))}));mxEvent.addListener(b,c,mxUtils.bind(this,function(b){this.isContainerEvent(b)&&a.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b))}));mxEvent.addListener(b,d,mxUtils.bind(this,function(b){this.isContainerEvent(b)&&a.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(b))}));mxEvent.addListener(b,"dblclick",mxUtils.bind(this,function(b){a.dblClick(b)}));var e=function(c){var d=null;if(mxClient.IS_TOUCH){d=mxEvent.getClientX(c);c=mxEvent.getClientY(c);c=mxUtils.convertPoint(b,
-d,c);d=a.view.getState(a.getCellAt(c.x,c.y))}return d};a.addMouseListener({mouseDown:function(){a.panningHandler.hideMenu()},mouseMove:function(){},mouseUp:function(){}});mxEvent.addListener(document,c,mxUtils.bind(this,function(b){a.tooltipHandler!=null&&a.tooltipHandler.isHideOnHover()&&a.tooltipHandler.hide();this.captureDocumentGesture&&(a.isMouseDown&&!mxEvent.isConsumed(b))&&a.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b,e(b)))}));mxEvent.addListener(document,d,mxUtils.bind(this,function(b){this.captureDocumentGesture&&
-a.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(b))}))}};
-mxGraphView.prototype.createHtml=function(){var a=this.graph.container;if(a!=null){this.canvas=this.createHtmlPane("100%","100%");this.backgroundPane=this.createHtmlPane("1px","1px");this.drawPane=this.createHtmlPane("1px","1px");this.overlayPane=this.createHtmlPane("1px","1px");this.canvas.appendChild(this.backgroundPane);this.canvas.appendChild(this.drawPane);this.canvas.appendChild(this.overlayPane);a.appendChild(this.canvas);if(mxClient.IS_QUIRKS){a=mxUtils.bind(this,function(){var a=this.getGraphBounds();
-this.updateHtmlCanvasSize(a.x+a.width+this.graph.border,a.y+a.height+this.graph.border)});mxEvent.addListener(window,"resize",a)}}};mxGraphView.prototype.updateHtmlCanvasSize=function(a,b){if(this.graph.container!=null){var c=this.graph.container.offsetHeight;this.canvas.style.width=this.graph.container.offsetWidth<a?a+"px":"100%";this.canvas.style.height=c<b?b+"px":"100%"}};
-mxGraphView.prototype.createHtmlPane=function(a,b){var c=document.createElement("DIV");if(a!=null&&b!=null){c.style.position="absolute";c.style.left="0px";c.style.top="0px";c.style.width=a;c.style.height=b}else c.style.position="relative";return c};
-mxGraphView.prototype.createVml=function(){var a=this.graph.container;if(a!=null){var b=a.offsetWidth,c=a.offsetHeight;this.canvas=this.createVmlPane(b,c);this.backgroundPane=this.createVmlPane(b,c);this.drawPane=this.createVmlPane(b,c);this.overlayPane=this.createVmlPane(b,c);this.canvas.appendChild(this.backgroundPane);this.canvas.appendChild(this.drawPane);this.canvas.appendChild(this.overlayPane);a.appendChild(this.canvas)}};
-mxGraphView.prototype.createVmlPane=function(a,b){var c=document.createElement("v:group");c.style.position="absolute";c.style.left="0px";c.style.top="0px";c.style.width=a+"px";c.style.height=b+"px";c.setAttribute("coordsize",a+","+b);c.setAttribute("coordorigin","0,0");return c};
-mxGraphView.prototype.createSvg=function(){var a=this.graph.container;this.canvas=document.createElementNS(mxConstants.NS_SVG,"g");this.backgroundPane=document.createElementNS(mxConstants.NS_SVG,"g");this.canvas.appendChild(this.backgroundPane);this.drawPane=document.createElementNS(mxConstants.NS_SVG,"g");this.canvas.appendChild(this.drawPane);this.overlayPane=document.createElementNS(mxConstants.NS_SVG,"g");this.canvas.appendChild(this.overlayPane);var b=document.createElementNS(mxConstants.NS_SVG,
-"svg");b.style.width="100%";b.style.height="100%";if(mxClient.IS_IE)b.style.marginBottom="-4px";b.appendChild(this.canvas);if(a!=null){a.appendChild(b);if(mxUtils.getCurrentStyle(a).position=="static")a.style.position="relative"}};
-mxGraphView.prototype.destroy=function(){var a=this.canvas!=null?this.canvas.ownerSVGElement:null;if(a==null)a=this.canvas;if(a!=null&&a.parentNode!=null){this.clear(this.currentRoot,true);mxEvent.removeAllListeners(document);mxEvent.release(this.graph.container);a.parentNode.removeChild(a);this.overlayPane=this.drawPane=this.backgroundPane=this.canvas=null}};
-function mxCurrentRootChange(a,b){this.view=a;this.previous=this.root=b;this.isUp=b==null;if(!this.isUp)for(var c=this.view.currentRoot,d=this.view.graph.getModel();c!=null;){if(c==b){this.isUp=true;break}c=d.getParent(c)}}
-mxCurrentRootChange.prototype.execute=function(){var a=this.view.currentRoot;this.view.currentRoot=this.previous;this.previous=a;a=this.view.graph.getTranslateForRoot(this.view.currentRoot);if(a!=null)this.view.translate=new mxPoint(-a.x,-a.y);this.view.fireEvent(new mxEventObject(this.isUp?mxEvent.UP:mxEvent.DOWN,"root",this.view.currentRoot,"previous",this.previous));if(this.isUp){this.view.clear(this.view.currentRoot,true);this.view.validate()}else this.view.refresh();this.isUp=!this.isUp};
-function mxGraph(a,b,c,d){this.mouseListeners=null;this.renderHint=c;this.dialect=mxClient.IS_SVG?mxConstants.DIALECT_SVG:c==mxConstants.RENDERING_HINT_EXACT&&mxClient.IS_VML?mxConstants.DIALECT_VML:c==mxConstants.RENDERING_HINT_FASTEST?mxConstants.DIALECT_STRICTHTML:c==mxConstants.RENDERING_HINT_FASTER?mxConstants.DIALECT_PREFERHTML:mxConstants.DIALECT_MIXEDHTML;this.model=b!=null?b:new mxGraphModel;this.multiplicities=[];this.imageBundles=[];this.cellRenderer=this.createCellRenderer();this.setSelectionModel(this.createSelectionModel());
-this.setStylesheet(d!=null?d:this.createStylesheet());this.view=this.createGraphView();this.graphModelChangeListener=mxUtils.bind(this,function(a,b){this.graphModelChanged(b.getProperty("edit").changes)});this.model.addListener(mxEvent.CHANGE,this.graphModelChangeListener);this.createHandlers();a!=null&&this.init(a);this.view.revalidate()}mxLoadResources&&mxResources.add(mxClient.basePath+"/resources/graph");mxGraph.prototype=new mxEventSource;mxGraph.prototype.constructor=mxGraph;
-mxGraph.prototype.EMPTY_ARRAY=[];mxGraph.prototype.mouseListeners=null;mxGraph.prototype.isMouseDown=!1;mxGraph.prototype.model=null;mxGraph.prototype.view=null;mxGraph.prototype.stylesheet=null;mxGraph.prototype.selectionModel=null;mxGraph.prototype.cellEditor=null;mxGraph.prototype.cellRenderer=null;mxGraph.prototype.multiplicities=null;mxGraph.prototype.renderHint=null;mxGraph.prototype.dialect=null;mxGraph.prototype.gridSize=10;mxGraph.prototype.gridEnabled=!0;mxGraph.prototype.portsEnabled=!0;
-mxGraph.prototype.doubleTapEnabled=!0;mxGraph.prototype.doubleTapTimeout=700;mxGraph.prototype.doubleTapTolerance=25;mxGraph.prototype.lastTouchY=0;mxGraph.prototype.lastTouchY=0;mxGraph.prototype.lastTouchTime=0;mxGraph.prototype.gestureEnabled=!0;mxGraph.prototype.tolerance=4;mxGraph.prototype.defaultOverlap=0.5;mxGraph.prototype.defaultParent=null;mxGraph.prototype.alternateEdgeStyle=null;mxGraph.prototype.backgroundImage=null;mxGraph.prototype.pageVisible=!1;
-mxGraph.prototype.pageBreaksVisible=!1;mxGraph.prototype.pageBreakColor="gray";mxGraph.prototype.pageBreakDashed=!0;mxGraph.prototype.minPageBreakDist=20;mxGraph.prototype.preferPageSize=!1;mxGraph.prototype.pageFormat=mxConstants.PAGE_FORMAT_A4_PORTRAIT;mxGraph.prototype.pageScale=1.5;mxGraph.prototype.enabled=!0;mxGraph.prototype.escapeEnabled=!0;mxGraph.prototype.invokesStopCellEditing=!0;mxGraph.prototype.enterStopsCellEditing=!1;mxGraph.prototype.useScrollbarsForPanning=!0;
-mxGraph.prototype.exportEnabled=!0;mxGraph.prototype.importEnabled=!0;mxGraph.prototype.cellsLocked=!1;mxGraph.prototype.cellsCloneable=!0;mxGraph.prototype.foldingEnabled=!0;mxGraph.prototype.cellsEditable=!0;mxGraph.prototype.cellsDeletable=!0;mxGraph.prototype.cellsMovable=!0;mxGraph.prototype.edgeLabelsMovable=!0;mxGraph.prototype.vertexLabelsMovable=!1;mxGraph.prototype.dropEnabled=!1;mxGraph.prototype.splitEnabled=!0;mxGraph.prototype.cellsResizable=!0;mxGraph.prototype.cellsBendable=!0;
-mxGraph.prototype.cellsSelectable=!0;mxGraph.prototype.cellsDisconnectable=!0;mxGraph.prototype.autoSizeCells=!1;mxGraph.prototype.autoScroll=!0;mxGraph.prototype.timerAutoScroll=!1;mxGraph.prototype.allowAutoPanning=!1;mxGraph.prototype.ignoreScrollbars=!1;mxGraph.prototype.autoExtend=!0;mxGraph.prototype.maximumGraphBounds=null;mxGraph.prototype.minimumGraphSize=null;mxGraph.prototype.minimumContainerSize=null;mxGraph.prototype.maximumContainerSize=null;mxGraph.prototype.resizeContainer=!1;
-mxGraph.prototype.border=0;mxGraph.prototype.ordered=!0;mxGraph.prototype.keepEdgesInForeground=!1;mxGraph.prototype.keepEdgesInBackground=!0;mxGraph.prototype.allowNegativeCoordinates=!0;mxGraph.prototype.constrainChildren=!0;mxGraph.prototype.extendParents=!0;mxGraph.prototype.extendParentsOnAdd=!0;mxGraph.prototype.collapseToPreferredSize=!0;mxGraph.prototype.zoomFactor=1.2;mxGraph.prototype.keepSelectionVisibleOnZoom=!1;mxGraph.prototype.centerZoom=!0;mxGraph.prototype.resetViewOnRootChange=!0;
-mxGraph.prototype.resetEdgesOnResize=!1;mxGraph.prototype.resetEdgesOnMove=!1;mxGraph.prototype.resetEdgesOnConnect=!0;mxGraph.prototype.allowLoops=!1;mxGraph.prototype.defaultLoopStyle=mxEdgeStyle.Loop;mxGraph.prototype.multigraph=!0;mxGraph.prototype.connectableEdges=!1;mxGraph.prototype.allowDanglingEdges=!0;mxGraph.prototype.cloneInvalidEdges=!1;mxGraph.prototype.disconnectOnMove=!0;mxGraph.prototype.labelsVisible=!0;mxGraph.prototype.htmlLabels=!1;mxGraph.prototype.swimlaneSelectionEnabled=!0;
-mxGraph.prototype.swimlaneNesting=!0;mxGraph.prototype.swimlaneIndicatorColorAttribute=mxConstants.STYLE_FILLCOLOR;mxGraph.prototype.imageBundles=null;mxGraph.prototype.minFitScale=0.1;mxGraph.prototype.maxFitScale=8;mxGraph.prototype.panDx=0;mxGraph.prototype.panDy=0;mxGraph.prototype.collapsedImage=new mxImage(mxClient.imageBasePath+"/collapsed.gif",9,9);mxGraph.prototype.expandedImage=new mxImage(mxClient.imageBasePath+"/expanded.gif",9,9);
-mxGraph.prototype.warningImage=new mxImage(mxClient.imageBasePath+"/warning"+(mxClient.IS_MAC?".png":".gif"),16,16);mxGraph.prototype.alreadyConnectedResource="none"!=mxClient.language?"alreadyConnected":"";mxGraph.prototype.containsValidationErrorsResource="none"!=mxClient.language?"containsValidationErrors":"";mxGraph.prototype.collapseExpandResource="none"!=mxClient.language?"collapse-expand":"";
-mxGraph.prototype.init=function(a){this.container=a;this.cellEditor=this.createCellEditor();this.view.init();this.sizeDidChange();if(mxClient.IS_IE){mxEvent.addListener(window,"unload",mxUtils.bind(this,function(){this.destroy()}));mxEvent.addListener(a,"selectstart",mxUtils.bind(this,function(){return this.isEditing()}))}};
-mxGraph.prototype.createHandlers=function(){this.tooltipHandler=new mxTooltipHandler(this);this.tooltipHandler.setEnabled(false);this.panningHandler=new mxPanningHandler(this);this.panningHandler.panningEnabled=false;this.selectionCellsHandler=new mxSelectionCellsHandler(this);this.connectionHandler=new mxConnectionHandler(this);this.connectionHandler.setEnabled(false);this.graphHandler=new mxGraphHandler(this)};mxGraph.prototype.createSelectionModel=function(){return new mxGraphSelectionModel(this)};
-mxGraph.prototype.createStylesheet=function(){return new mxStylesheet};mxGraph.prototype.createGraphView=function(){return new mxGraphView(this)};mxGraph.prototype.createCellRenderer=function(){return new mxCellRenderer};mxGraph.prototype.createCellEditor=function(){return new mxCellEditor(this)};mxGraph.prototype.getModel=function(){return this.model};mxGraph.prototype.getView=function(){return this.view};mxGraph.prototype.getStylesheet=function(){return this.stylesheet};
-mxGraph.prototype.setStylesheet=function(a){this.stylesheet=a};mxGraph.prototype.getSelectionModel=function(){return this.selectionModel};mxGraph.prototype.setSelectionModel=function(a){this.selectionModel=a};
-mxGraph.prototype.getSelectionCellsForChanges=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];if(d.constructor!=mxRootChange){var e=null;if(d instanceof mxChildChange&&d.previous==null)e=d.child;else if(d.cell!=null&&d.cell instanceof mxCell)e=d.cell;e!=null&&mxUtils.indexOf(b,e)<0&&b.push(e)}}return this.getModel().getTopmostCells(b)};
-mxGraph.prototype.graphModelChanged=function(a){for(var b=0;b<a.length;b++)this.processChange(a[b]);this.removeSelectionCells(this.getRemovedCellsForChanges(a));this.view.validate();this.sizeDidChange()};mxGraph.prototype.getRemovedCellsForChanges=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];if(d instanceof mxRootChange)break;else d instanceof mxChildChange?d.previous!=null&&d.parent==null&&(b=b.concat(this.model.getDescendants(d.child))):d instanceof mxVisibleChange&&(b=b.concat(this.model.getDescendants(d.cell)))}return b};
-mxGraph.prototype.processChange=function(a){if(a instanceof mxRootChange){this.clearSelection();this.removeStateForCell(a.previous);if(this.resetViewOnRootChange){this.view.scale=1;this.view.translate.x=0;this.view.translate.y=0}this.fireEvent(new mxEventObject(mxEvent.ROOT))}else if(a instanceof mxChildChange){var b=this.model.getParent(a.child);if(b!=null)this.view.invalidate(a.child,true,false,a.previous!=null);else{this.removeStateForCell(a.child);this.view.currentRoot==a.child&&this.home()}if(b!=
-a.previous){b!=null&&this.view.invalidate(b,false,false);a.previous!=null&&this.view.invalidate(a.previous,false,false)}}else if(a instanceof mxTerminalChange||a instanceof mxGeometryChange)this.view.invalidate(a.cell);else if(a instanceof mxValueChange)this.view.invalidate(a.cell,false,false);else if(a instanceof mxStyleChange){this.view.invalidate(a.cell,true,true,false);this.view.removeState(a.cell)}else a.cell!=null&&a.cell instanceof mxCell&&this.removeStateForCell(a.cell)};
-mxGraph.prototype.removeStateForCell=function(a){for(var b=this.model.getChildCount(a),c=0;c<b;c++)this.removeStateForCell(this.model.getChildAt(a,c));this.view.removeState(a)};mxGraph.prototype.addCellOverlay=function(a,b){if(a.overlays==null)a.overlays=[];a.overlays.push(b);var c=this.view.getState(a);c!=null&&this.cellRenderer.redraw(c);this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY,"cell",a,"overlay",b));return b};mxGraph.prototype.getCellOverlays=function(a){return a.overlays};
-mxGraph.prototype.removeCellOverlay=function(a,b){if(b==null)this.removeCellOverlays(a);else{var c=mxUtils.indexOf(a.overlays,b);if(c>=0){a.overlays.splice(c,1);if(a.overlays.length==0)a.overlays=null;c=this.view.getState(a);c!=null&&this.cellRenderer.redraw(c);this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,"cell",a,"overlay",b))}else b=null}return b};
-mxGraph.prototype.removeCellOverlays=function(a){var b=a.overlays;if(b!=null){a.overlays=null;var c=this.view.getState(a);c!=null&&this.cellRenderer.redraw(c);for(c=0;c<b.length;c++)this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,"cell",a,"overlay",b[c]))}return b};mxGraph.prototype.clearCellOverlays=function(a){a=a!=null?a:this.model.getRoot();this.removeCellOverlays(a);for(var b=this.model.getChildCount(a),c=0;c<b;c++)this.clearCellOverlays(this.model.getChildAt(a,c))};
-mxGraph.prototype.setCellWarning=function(a,b,c,d){if(b!=null&&b.length>0){c=c!=null?c:this.warningImage;b=new mxCellOverlay(c,"<font color=red>"+b+"</font>");d&&b.addListener(mxEvent.CLICK,mxUtils.bind(this,function(){this.isEnabled()&&this.setSelectionCell(a)}));return this.addCellOverlay(a,b)}this.removeCellOverlays(a);return null};mxGraph.prototype.startEditing=function(a){this.startEditingAtCell(null,a)};
-mxGraph.prototype.startEditingAtCell=function(a,b){if(a==null){a=this.getSelectionCell();a!=null&&!this.isCellEditable(a)&&(a=null)}if(a!=null){this.fireEvent(new mxEventObject(mxEvent.START_EDITING,"cell",a,"event",b));this.cellEditor.startEditing(a,b)}};mxGraph.prototype.getEditingValue=function(a){return this.convertValueToString(a)};mxGraph.prototype.stopEditing=function(a){this.cellEditor.stopEditing(a)};
-mxGraph.prototype.labelChanged=function(a,b,c){this.model.beginUpdate();try{this.cellLabelChanged(a,b,this.isAutoSizeCell(a));this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,"cell",a,"value",b,"event",c))}finally{this.model.endUpdate()}return a};mxGraph.prototype.cellLabelChanged=function(a,b,c){this.model.beginUpdate();try{this.model.setValue(a,b);c&&this.cellSizeUpdated(a,false)}finally{this.model.endUpdate()}};
-mxGraph.prototype.escape=function(){this.stopEditing(true);this.connectionHandler.reset();this.graphHandler.reset();for(var a=this.getSelectionCells(),b=0;b<a.length;b++){var c=this.view.getState(a[b]);c!=null&&c.handler!=null&&c.handler.reset()}};
-mxGraph.prototype.click=function(a){var b=a.getEvent(),c=a.getCell(),d=new mxEventObject(mxEvent.CLICK,"event",b,"cell",c);a.isConsumed()&&d.consume();this.fireEvent(d);if(this.isEnabled()&&!mxEvent.isConsumed(b)&&!d.isConsumed())if(c!=null)this.selectCellForEvent(c,b);else{c=null;this.isSwimlaneSelectionEnabled()&&(c=this.getSwimlaneAt(a.getGraphX(),a.getGraphY()));c!=null?this.selectCellForEvent(c,b):this.isToggleEvent(b)||this.clearSelection()}};
-mxGraph.prototype.dblClick=function(a,b){var c=new mxEventObject(mxEvent.DOUBLE_CLICK,"event",a,"cell",b);this.fireEvent(c);this.isEnabled()&&(!mxEvent.isConsumed(a)&&!c.isConsumed()&&b!=null&&this.isCellEditable(b))&&this.startEditingAtCell(b,a)};
-mxGraph.prototype.scrollPointToVisible=function(a,b,c,d){if(!this.timerAutoScroll&&(this.ignoreScrollbars||mxUtils.hasScrollbars(this.container))){var e=this.container,d=d!=null?d:20;if(a>=e.scrollLeft&&b>=e.scrollTop&&a<=e.scrollLeft+e.clientWidth&&b<=e.scrollTop+e.clientHeight){var f=e.scrollLeft+e.clientWidth-a;if(f<d){a=e.scrollLeft;e.scrollLeft=e.scrollLeft+(d-f);if(c&&a==e.scrollLeft){if(this.dialect==mxConstants.DIALECT_SVG){var a=this.view.getDrawPane().ownerSVGElement,g=this.container.scrollWidth+
-d-f;a.setAttribute("width",g)}else{g=Math.max(e.clientWidth,e.scrollWidth)+d-f;a=this.view.getCanvas();a.style.width=g+"px"}e.scrollLeft=e.scrollLeft+(d-f)}}else{f=a-e.scrollLeft;if(f<d)e.scrollLeft=e.scrollLeft-(d-f)}f=e.scrollTop+e.clientHeight-b;if(f<d){a=e.scrollTop;e.scrollTop=e.scrollTop+(d-f);if(a==e.scrollTop&&c){if(this.dialect==mxConstants.DIALECT_SVG){a=this.view.getDrawPane().ownerSVGElement;b=this.container.scrollHeight+d-f;a.setAttribute("height",b)}else{b=Math.max(e.clientHeight,e.scrollHeight)+
-d-f;a=this.view.getCanvas();a.style.height=b+"px"}e.scrollTop=e.scrollTop+(d-f)}}else{f=b-e.scrollTop;if(f<d)e.scrollTop=e.scrollTop-(d-f)}}}else if(this.allowAutoPanning&&!this.panningHandler.active){if(this.panningManager==null)this.panningManager=this.createPanningManager();this.panningManager.panTo(a+this.panDx,b+this.panDy)}};mxGraph.prototype.createPanningManager=function(){return new mxPanningManager(this)};
-mxGraph.prototype.getBorderSizes=function(){function a(a){var b=0,b=a=="thin"?2:a=="medium"?4:a=="thick"?6:parseInt(a);isNaN(b)&&(b=0);return b}var b=mxUtils.getCurrentStyle(this.container),c=new mxRectangle;c.x=a(b.borderLeftWidth)+parseInt(b.paddingLeft||0);c.y=a(b.borderTopWidth)+parseInt(b.paddingTop||0);c.width=a(b.borderRightWidth)+parseInt(b.paddingRight||0);c.height=a(b.borderBottomWidth)+parseInt(b.paddingBottom||0);return c};
-mxGraph.prototype.getPreferredPageSize=function(a,b,c){var a=this.view.scale,d=this.view.translate,e=this.pageFormat,f=a*this.pageScale,e=new mxRectangle(0,0,e.width*f,e.height*f),b=this.pageBreaksVisible?Math.ceil(b/e.width):1,c=this.pageBreaksVisible?Math.ceil(c/e.height):1;return new mxRectangle(0,0,b*e.width+2+d.x/a,c*e.height+2+d.y/a)};
-mxGraph.prototype.sizeDidChange=function(){var a=this.getGraphBounds();if(this.container!=null){var b=this.getBorder(),c=Math.max(0,a.x+a.width+1+b),b=Math.max(0,a.y+a.height+1+b);if(this.minimumContainerSize!=null){c=Math.max(c,this.minimumContainerSize.width);b=Math.max(b,this.minimumContainerSize.height)}this.resizeContainer&&this.doResizeContainer(c,b);if(this.preferPageSize||!mxClient.IS_IE&&this.pageVisible){var d=this.getPreferredPageSize(a,c,b);if(d!=null){c=d.width;b=d.height}}if(this.minimumGraphSize!=
-null){c=Math.max(c,this.minimumGraphSize.width*this.view.scale);b=Math.max(b,this.minimumGraphSize.height*this.view.scale)}c=Math.ceil(c-1);b=Math.ceil(b-1);if(this.dialect==mxConstants.DIALECT_SVG){d=this.view.getDrawPane().ownerSVGElement;d.style.minWidth=Math.max(1,c)+"px";d.style.minHeight=Math.max(1,b)+"px"}else if(mxClient.IS_QUIRKS)this.view.updateHtmlCanvasSize(Math.max(1,c),Math.max(1,b));else{this.view.canvas.style.minWidth=Math.max(1,c)+"px";this.view.canvas.style.minHeight=Math.max(1,
-b)+"px"}this.updatePageBreaks(this.pageBreaksVisible,c-1,b-1)}this.fireEvent(new mxEventObject(mxEvent.SIZE,"bounds",a))};
-mxGraph.prototype.doResizeContainer=function(a,b){if(mxClient.IS_IE)if(mxClient.IS_QUIRKS)var c=this.getBorderSizes(),a=a+Math.max(2,c.x+c.width+1),b=b+Math.max(2,c.y+c.height+1);else if(document.documentMode>=9){a=a+3;b=b+5}else{a=a+1;b=b+1}else b=b+1;if(this.maximumContainerSize!=null){a=Math.min(this.maximumContainerSize.width,a);b=Math.min(this.maximumContainerSize.height,b)}this.container.style.width=Math.ceil(a)+"px";this.container.style.height=Math.ceil(b)+"px"};
-mxGraph.prototype.updatePageBreaks=function(a,b,c){var d=this.view.scale,e=this.view.translate,f=this.pageFormat,g=d*this.pageScale,e=new mxRectangle(d*e.x,d*e.y,f.width*g,f.height*g),a=a&&Math.min(e.width,e.height)>this.minPageBreakDist;e.x=mxUtils.mod(e.x,e.width);e.y=mxUtils.mod(e.y,e.height);f=a?Math.ceil((b-e.x)/e.width):0;a=a?Math.ceil((c-e.y)/e.height):0;if(this.horizontalPageBreaks==null&&f>0)this.horizontalPageBreaks=[];if(this.horizontalPageBreaks!=null){for(g=0;g<=f;g++){var h=[new mxPoint(e.x+
-g*e.width,1),new mxPoint(e.x+g*e.width,c)];if(this.horizontalPageBreaks[g]!=null){this.horizontalPageBreaks[g].scale=1;this.horizontalPageBreaks[g].points=h;this.horizontalPageBreaks[g].redraw()}else{h=new mxPolyline(h,this.pageBreakColor,this.scale);h.dialect=this.dialect;h.isDashed=this.pageBreakDashed;h.scale=d;h.crisp=true;h.init(this.view.backgroundPane);h.redraw();this.horizontalPageBreaks[g]=h}}for(g=f;g<this.horizontalPageBreaks.length;g++)this.horizontalPageBreaks[g].destroy();this.horizontalPageBreaks.splice(f,
-this.horizontalPageBreaks.length-f)}if(this.verticalPageBreaks==null&&a>0)this.verticalPageBreaks=[];if(this.verticalPageBreaks!=null){for(g=0;g<=a;g++){h=[new mxPoint(1,e.y+g*e.height),new mxPoint(b,e.y+g*e.height)];if(this.verticalPageBreaks[g]!=null){this.verticalPageBreaks[g].scale=1;this.verticalPageBreaks[g].points=h;this.verticalPageBreaks[g].redraw()}else{h=new mxPolyline(h,this.pageBreakColor,d);h.dialect=this.dialect;h.isDashed=this.pageBreakDashed;h.scale=d;h.crisp=true;h.init(this.view.backgroundPane);
-h.redraw();this.verticalPageBreaks[g]=h}}for(g=a;g<this.verticalPageBreaks.length;g++)this.verticalPageBreaks[g].destroy();this.verticalPageBreaks.splice(a,this.verticalPageBreaks.length-a)}};mxGraph.prototype.getCellStyle=function(a){var b=this.model.getStyle(a),c=null,c=this.model.isEdge(a)?this.stylesheet.getDefaultEdgeStyle():this.stylesheet.getDefaultVertexStyle();b!=null&&(c=this.postProcessCellStyle(this.stylesheet.getCellStyle(b,c)));if(c==null)c=mxGraph.prototype.EMPTY_ARRAY;return c};
-mxGraph.prototype.postProcessCellStyle=function(a){if(a!=null){var b=a[mxConstants.STYLE_IMAGE],c=this.getImageFromBundles(b);c!=null?a[mxConstants.STYLE_IMAGE]=c:c=b;if(c!=null&&c.substring(0,11)=="data:image/"){b=c.indexOf(",");b>0&&(c=c.substring(0,b)+";base64,"+c.substring(b+1));a[mxConstants.STYLE_IMAGE]=c}}return a};mxGraph.prototype.setCellStyle=function(a,b){b=b||this.getSelectionCells();if(b!=null){this.model.beginUpdate();try{for(var c=0;c<b.length;c++)this.model.setStyle(b[c],a)}finally{this.model.endUpdate()}}};
-mxGraph.prototype.toggleCellStyle=function(a,b,c){c=c||this.getSelectionCell();this.toggleCellStyles(a,b,[c])};mxGraph.prototype.toggleCellStyles=function(a,b,c){b=b!=null?b:false;c=c||this.getSelectionCells();if(c!=null&&c.length>0){var d=this.view.getState(c[0]),d=d!=null?d.style:this.getCellStyle(c[0]);if(d!=null){b=mxUtils.getValue(d,a,b)?0:1;this.setCellStyles(a,b,c)}}};mxGraph.prototype.setCellStyles=function(a,b,c){c=c||this.getSelectionCells();mxUtils.setCellStyles(this.model,c,a,b)};
-mxGraph.prototype.toggleCellStyleFlags=function(a,b,c){this.setCellStyleFlags(a,b,null,c)};mxGraph.prototype.setCellStyleFlags=function(a,b,c,d){d=d||this.getSelectionCells();if(d!=null&&d.length>0){if(c==null){var e=this.view.getState(d[0]),e=e!=null?e.style:this.getCellStyle(d[0]);e!=null&&(c=(parseInt(e[a]||0)&b)!=b)}mxUtils.setCellStyleFlags(this.model,d,a,b,c)}};
-mxGraph.prototype.alignCells=function(a,b,c){b==null&&(b=this.getSelectionCells());if(b!=null&&b.length>1){if(c==null)for(var d=0;d<b.length;d++){var e=this.getCellGeometry(b[d]);if(e!=null&&!this.model.isEdge(b[d]))if(c==null)if(a==mxConstants.ALIGN_CENTER){c=e.x+e.width/2;break}else if(a==mxConstants.ALIGN_RIGHT)c=e.x+e.width;else if(a==mxConstants.ALIGN_TOP)c=e.y;else if(a==mxConstants.ALIGN_MIDDLE){c=e.y+e.height/2;break}else c=a==mxConstants.ALIGN_BOTTOM?e.y+e.height:e.x;else c=a==mxConstants.ALIGN_RIGHT?
-Math.max(c,e.x+e.width):a==mxConstants.ALIGN_TOP?Math.min(c,e.y):a==mxConstants.ALIGN_BOTTOM?Math.max(c,e.y+e.height):Math.min(c,e.x)}if(c!=null){this.model.beginUpdate();try{for(d=0;d<b.length;d++){e=this.getCellGeometry(b[d]);if(e!=null&&!this.model.isEdge(b[d])){e=e.clone();a==mxConstants.ALIGN_CENTER?e.x=c-e.width/2:a==mxConstants.ALIGN_RIGHT?e.x=c-e.width:a==mxConstants.ALIGN_TOP?e.y=c:a==mxConstants.ALIGN_MIDDLE?e.y=c-e.height/2:a==mxConstants.ALIGN_BOTTOM?e.y=c-e.height:e.x=c;this.model.setGeometry(b[d],
-e)}}this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS,"align",a,"cells",b))}finally{this.model.endUpdate()}}}return b};mxGraph.prototype.flipEdge=function(a){if(a!=null&&this.alternateEdgeStyle!=null){this.model.beginUpdate();try{var b=this.model.getStyle(a);b==null||b.length==0?this.model.setStyle(a,this.alternateEdgeStyle):this.model.setStyle(a,null);this.resetEdge(a);this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE,"edge",a))}finally{this.model.endUpdate()}}return a};
-mxGraph.prototype.addImageBundle=function(a){this.imageBundles.push(a)};mxGraph.prototype.removeImageBundle=function(a){for(var b=[],c=0;c<this.imageBundles.length;c++)this.imageBundles[c]!=a&&b.push(this.imageBundles[c]);this.imageBundles=b};mxGraph.prototype.getImageFromBundles=function(a){if(a!=null)for(var b=0;b<this.imageBundles.length;b++){var c=this.imageBundles[b].getImage(a);if(c!=null)return c}return null};
-mxGraph.prototype.orderCells=function(a,b){b==null&&(b=mxUtils.sortCells(this.getSelectionCells(),true));this.model.beginUpdate();try{this.cellsOrdered(b,a);this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS,"back",a,"cells",b))}finally{this.model.endUpdate()}return b};
-mxGraph.prototype.cellsOrdered=function(a,b){if(a!=null){this.model.beginUpdate();try{for(var c=0;c<a.length;c++){var d=this.model.getParent(a[c]);b?this.model.add(d,a[c],c):this.model.add(d,a[c],this.model.getChildCount(d)-1)}this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED,"back",b,"cells",a))}finally{this.model.endUpdate()}}};
-mxGraph.prototype.groupCells=function(a,b,c){c==null&&(c=mxUtils.sortCells(this.getSelectionCells(),true));c=this.getCellsForGroup(c);a==null&&(a=this.createGroupCell(c));var d=this.getBoundsForGroup(a,c,b);if(c.length>0&&d!=null){var e=this.model.getParent(a);e==null&&(e=this.model.getParent(c[0]));this.model.beginUpdate();try{this.getCellGeometry(a)==null&&this.model.setGeometry(a,new mxGeometry);var f=this.model.getChildCount(a);this.cellsAdded(c,a,f,null,null,false,false);this.cellsMoved(c,-d.x,
--d.y,false,true);f=this.model.getChildCount(e);this.cellsAdded([a],e,f,null,null,false);this.cellsResized([a],[d]);this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS,"group",a,"border",b,"cells",c))}finally{this.model.endUpdate()}}return a};mxGraph.prototype.getCellsForGroup=function(a){var b=[];if(a!=null&&a.length>0){var c=this.model.getParent(a[0]);b.push(a[0]);for(var d=1;d<a.length;d++)this.model.getParent(a[d])==c&&b.push(a[d])}return b};
-mxGraph.prototype.getBoundsForGroup=function(a,b,c){b=this.getBoundingBoxFromGeometry(b);if(b!=null){if(this.isSwimlane(a)){a=this.getStartSize(a);b.x=b.x-a.width;b.y=b.y-a.height;b.width=b.width+a.width;b.height=b.height+a.height}b.x=b.x-c;b.y=b.y-c;b.width=b.width+2*c;b.height=b.height+2*c}return b};mxGraph.prototype.createGroupCell=function(){var a=new mxCell("");a.setVertex(true);a.setConnectable(false);return a};
-mxGraph.prototype.ungroupCells=function(a){var b=[];if(a==null){for(var a=this.getSelectionCells(),c=[],d=0;d<a.length;d++)this.model.getChildCount(a[d])>0&&c.push(a[d]);a=c}if(a!=null&&a.length>0){this.model.beginUpdate();try{for(d=0;d<a.length;d++){var e=this.model.getChildren(a[d]);if(e!=null&&e.length>0){var e=e.slice(),f=this.model.getParent(a[d]),g=this.model.getChildCount(f);this.cellsAdded(e,f,g,null,null,true);b=b.concat(e)}}this.cellsRemoved(this.addAllEdges(a));this.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS,
-"cells",a))}finally{this.model.endUpdate()}}return b};mxGraph.prototype.removeCellsFromParent=function(a){a==null&&(a=this.getSelectionCells());this.model.beginUpdate();try{var b=this.getDefaultParent(),c=this.model.getChildCount(b);this.cellsAdded(a,b,c,null,null,true);this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT,"cells",a))}finally{this.model.endUpdate()}return a};
-mxGraph.prototype.updateGroupBounds=function(a,b,c){a==null&&(a=this.getSelectionCells());b=b!=null?b:0;c=c!=null?c:false;this.model.beginUpdate();try{for(var d=0;d<a.length;d++){var e=this.getCellGeometry(a[d]);if(e!=null){var f=this.getChildCells(a[d]);if(f!=null&&f.length>0){var g=this.getBoundingBoxFromGeometry(f);if(g.width>0&&g.height>0){var h=this.isSwimlane(a[d])?this.getStartSize(a[d]):new mxRectangle,e=e.clone();if(c){e.x=e.x+(g.x-h.width-b);e.y=e.y+(g.y-h.height-b)}e.width=g.width+h.width+
-2*b;e.height=g.height+h.height+2*b;this.model.setGeometry(a[d],e);this.moveCells(f,-g.x+h.width+b,-g.y+h.height+b)}}}}}finally{this.model.endUpdate()}return a};
-mxGraph.prototype.cloneCells=function(a,b){var b=b!=null?b:true,c=null;if(a!=null){for(var d={},c=[],e=0;e<a.length;e++){var f=mxCellPath.create(a[e]);d[f]=a[e];c.push(a[e])}if(c.length>0)for(var f=this.view.scale,g=this.view.translate,c=this.model.cloneCells(a,true),e=0;e<a.length;e++)if(!b&&this.model.isEdge(c[e])&&this.getEdgeValidationError(c[e],this.model.getTerminal(c[e],true),this.model.getTerminal(c[e],false))!=null)c[e]=null;else{var h=this.model.getGeometry(c[e]);if(h!=null){var k=this.view.getState(a[e]),
-i=this.view.getState(this.model.getParent(a[e]));if(k!=null&&i!=null){var l=i.origin.x,i=i.origin.y;if(this.model.isEdge(c[e])){for(var k=k.absolutePoints,m=this.model.getTerminal(a[e],true),n=mxCellPath.create(m);m!=null&&d[n]==null;){m=this.model.getParent(m);n=mxCellPath.create(m)}m==null&&h.setTerminalPoint(new mxPoint(k[0].x/f-g.x,k[0].y/f-g.y),true);m=this.model.getTerminal(a[e],false);for(n=mxCellPath.create(m);m!=null&&d[n]==null;){m=this.model.getParent(m);n=mxCellPath.create(m)}if(m==null){m=
-k.length-1;h.setTerminalPoint(new mxPoint(k[m].x/f-g.x,k[m].y/f-g.y),false)}h=h.points;if(h!=null)for(k=0;k<h.length;k++){h[k].x=h[k].x+l;h[k].y=h[k].y+i}}else{h.x=h.x+l;h.y=h.y+i}}}}else c=[]}return c};mxGraph.prototype.insertVertex=function(a,b,c,d,e,f,g,h,k){return this.addCell(this.createVertex(a,b,c,d,e,f,g,h,k),a)};
-mxGraph.prototype.createVertex=function(a,b,c,d,e,f,g,h,k){a=new mxGeometry(d,e,f,g);a.relative=k!=null?k:false;c=new mxCell(c,a,h);c.setId(b);c.setVertex(true);c.setConnectable(true);return c};mxGraph.prototype.insertEdge=function(a,b,c,d,e,f){return this.addEdge(this.createEdge(a,b,c,d,e,f),a,d,e)};mxGraph.prototype.createEdge=function(a,b,c,d,e,f){a=new mxCell(c,new mxGeometry,f);a.setId(b);a.setEdge(true);a.geometry.relative=true;return a};
-mxGraph.prototype.addEdge=function(a,b,c,d,e){return this.addCell(a,b,e,c,d)};mxGraph.prototype.addCell=function(a,b,c,d,e){return this.addCells([a],b,c,d,e)[0]};mxGraph.prototype.addCells=function(a,b,c,d,e){b==null&&(b=this.getDefaultParent());c==null&&(c=this.model.getChildCount(b));this.model.beginUpdate();try{this.cellsAdded(a,b,c,d,e,false,true);this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS,"cells",a,"parent",b,"index",c,"source",d,"target",e))}finally{this.model.endUpdate()}return a};
-mxGraph.prototype.cellsAdded=function(a,b,c,d,e,f,g){if(a!=null&&b!=null&&c!=null){this.model.beginUpdate();try{for(var h=f?this.view.getState(b):null,k=h!=null?h.origin:null,i=new mxPoint(0,0),h=0;h<a.length;h++)if(a[h]==null)c--;else{var l=this.model.getParent(a[h]);if(k!=null&&a[h]!=b&&b!=l){var m=this.view.getState(l),n=m!=null?m.origin:i,o=this.model.getGeometry(a[h]);if(o!=null){var p=n.x-k.x,q=n.y-k.y,o=o.clone();o.translate(p,q);if(!o.relative&&this.model.isVertex(a[h])&&!this.isAllowNegativeCoordinates()){o.x=
-Math.max(0,o.x);o.y=Math.max(0,o.y)}this.model.setGeometry(a[h],o)}}b==l&&c--;this.model.add(b,a[h],c+h);this.isExtendParentsOnAdd()&&this.isExtendParent(a[h])&&this.extendParent(a[h]);(g==null||g)&&this.constrainChild(a[h]);d!=null&&this.cellConnected(a[h],d,true);e!=null&&this.cellConnected(a[h],e,false)}this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED,"cells",a,"parent",b,"index",c,"source",d,"target",e,"absolute",f))}finally{this.model.endUpdate()}}};
-mxGraph.prototype.removeCells=function(a,b){b=b!=null?b:true;a==null&&(a=this.getDeletableCells(this.getSelectionCells()));b&&(a=this.getDeletableCells(this.addAllEdges(a)));this.model.beginUpdate();try{this.cellsRemoved(a);this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS,"cells",a,"includeEdges",b))}finally{this.model.endUpdate()}return a};
-mxGraph.prototype.cellsRemoved=function(a){if(a!=null&&a.length>0){var b=this.view.scale,c=this.view.translate;this.model.beginUpdate();try{for(var d={},e=0;e<a.length;e++){var f=mxCellPath.create(a[e]);d[f]=a[e]}for(e=0;e<a.length;e++){for(var g=this.getConnections(a[e]),h=0;h<g.length;h++){f=mxCellPath.create(g[h]);if(d[f]==null){var k=this.model.getGeometry(g[h]);if(k!=null){var i=this.view.getState(g[h]);if(i!=null){var k=k.clone(),l=i.getVisibleTerminal(true)==a[e],m=i.absolutePoints,n=l?0:m.length-
-1;k.setTerminalPoint(new mxPoint(m[n].x/b-c.x,m[n].y/b-c.y),l);this.model.setTerminal(g[h],null,l);this.model.setGeometry(g[h],k)}}}}this.model.remove(a[e])}this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED,"cells",a))}finally{this.model.endUpdate()}}};
-mxGraph.prototype.splitEdge=function(a,b,c,d,e){d=d||0;e=e||0;c==null&&(c=this.cloneCells([a])[0]);var f=this.model.getParent(a),g=this.model.getTerminal(a,true);this.model.beginUpdate();try{this.cellsMoved(b,d,e,false,false);this.cellsAdded(b,f,this.model.getChildCount(f),null,null,true);this.cellsAdded([c],f,this.model.getChildCount(f),g,b[0],false);this.cellConnected(a,b[0],true);this.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE,"edge",a,"cells",b,"newEdge",c,"dx",d,"dy",e))}finally{this.model.endUpdate()}return c};
-mxGraph.prototype.toggleCells=function(a,b,c){b==null&&(b=this.getSelectionCells());c&&(b=this.addAllEdges(b));this.model.beginUpdate();try{this.cellsToggled(b,a);this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS,"show",a,"cells",b,"includeEdges",c))}finally{this.model.endUpdate()}return b};mxGraph.prototype.cellsToggled=function(a,b){if(a!=null&&a.length>0){this.model.beginUpdate();try{for(var c=0;c<a.length;c++)this.model.setVisible(a[c],b)}finally{this.model.endUpdate()}}};
-mxGraph.prototype.foldCells=function(a,b,c,d){b=b!=null?b:false;c==null&&(c=this.getFoldableCells(this.getSelectionCells(),a));this.stopEditing(false);this.model.beginUpdate();try{this.cellsFolded(c,a,b,d);this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS,"collapse",a,"recurse",b,"cells",c))}finally{this.model.endUpdate()}return c};
-mxGraph.prototype.cellsFolded=function(a,b,c,d){if(a!=null&&a.length>0){this.model.beginUpdate();try{for(var e=0;e<a.length;e++)if((!d||this.isCellFoldable(a[e],b))&&b!=this.isCellCollapsed(a[e])){this.model.setCollapsed(a[e],b);this.swapBounds(a[e],b);this.isExtendParent(a[e])&&this.extendParent(a[e]);c&&this.foldCells(this.model.getChildren(a[e]),b,c)}this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED,"cells",a,"collapse",b,"recurse",c))}finally{this.model.endUpdate()}}};
-mxGraph.prototype.swapBounds=function(a,b){if(a!=null){var c=this.model.getGeometry(a);if(c!=null){c=c.clone();this.updateAlternateBounds(a,c,b);c.swap();this.model.setGeometry(a,c)}}};
-mxGraph.prototype.updateAlternateBounds=function(a,b){if(a!=null&&b!=null)if(b.alternateBounds==null){var c=b;if(this.collapseToPreferredSize){var d=this.getPreferredSizeForCell(a);if(d!=null){c=d;d=this.view.getState(a);d=d!=null?d.style:this.getCellStyle(a);d=mxUtils.getValue(d,mxConstants.STYLE_STARTSIZE);if(d>0)c.height=Math.max(c.height,d)}}b.alternateBounds=new mxRectangle(b.x,b.y,c.width,c.height)}else{b.alternateBounds.x=b.x;b.alternateBounds.y=b.y}};
-mxGraph.prototype.addAllEdges=function(a){var b=a.slice();return b=b.concat(this.getAllEdges(a))};mxGraph.prototype.getAllEdges=function(a){var b=[];if(a!=null)for(var c=0;c<a.length;c++){for(var d=this.model.getEdgeCount(a[c]),e=0;e<d;e++)b.push(this.model.getEdgeAt(a[c],e));d=this.model.getChildren(a[c]);b=b.concat(this.getAllEdges(d))}return b};
-mxGraph.prototype.updateCellSize=function(a,b){b=b!=null?b:false;this.model.beginUpdate();try{this.cellSizeUpdated(a,b);this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE,"cell",a,"ignoreChildren",b))}finally{this.model.endUpdate()}return a};
-mxGraph.prototype.cellSizeUpdated=function(a,b){if(a!=null){this.model.beginUpdate();try{var c=this.getPreferredSizeForCell(a),d=this.model.getGeometry(a);if(c!=null&&d!=null){var e=this.isCellCollapsed(a),d=d.clone();if(this.isSwimlane(a)){var f=this.view.getState(a),g=f!=null?f.style:this.getCellStyle(a),h=this.model.getStyle(a);h==null&&(h="");if(mxUtils.getValue(g,mxConstants.STYLE_HORIZONTAL,true)){h=mxUtils.setStyle(h,mxConstants.STYLE_STARTSIZE,c.height+8);if(e)d.height=c.height+8;d.width=
-c.width}else{h=mxUtils.setStyle(h,mxConstants.STYLE_STARTSIZE,c.width+8);if(e)d.width=c.width+8;d.height=c.height}this.model.setStyle(a,h)}else{d.width=c.width;d.height=c.height}if(!b&&!e){var k=this.view.getBounds(this.model.getChildren(a));if(k!=null){var i=this.view.translate,l=this.view.scale,m=(k.y+k.height)/l-d.y-i.y;d.width=Math.max(d.width,(k.x+k.width)/l-d.x-i.x);d.height=Math.max(d.height,m)}}this.cellsResized([a],[d])}}finally{this.model.endUpdate()}}};
-mxGraph.prototype.getPreferredSizeForCell=function(a){var b=null;if(a!=null){var c=this.view.getState(a),d=c!=null?c.style:this.getCellStyle(a);if(d!=null&&!this.model.isEdge(a)){var e=d[mxConstants.STYLE_FONTSIZE]||mxConstants.DEFAULT_FONTSIZE,f=0,b=0;if((this.getImage(c)!=null||d[mxConstants.STYLE_IMAGE]!=null)&&d[mxConstants.STYLE_SHAPE]==mxConstants.SHAPE_LABEL){d[mxConstants.STYLE_VERTICAL_ALIGN]==mxConstants.ALIGN_MIDDLE&&(f=f+(parseFloat(d[mxConstants.STYLE_IMAGE_WIDTH])||mxLabel.prototype.imageSize));
-d[mxConstants.STYLE_ALIGN]!=mxConstants.ALIGN_CENTER&&(b=b+(parseFloat(d[mxConstants.STYLE_IMAGE_HEIGHT])||mxLabel.prototype.imageSize))}f=f+2*(d[mxConstants.STYLE_SPACING]||0);f=f+(d[mxConstants.STYLE_SPACING_LEFT]||0);f=f+(d[mxConstants.STYLE_SPACING_RIGHT]||0);b=b+2*(d[mxConstants.STYLE_SPACING]||0);b=b+(d[mxConstants.STYLE_SPACING_TOP]||0);b=b+(d[mxConstants.STYLE_SPACING_BOTTOM]||0);c=this.getFoldingImage(c);c!=null&&(f=f+(c.width+8));c=this.getLabel(a);if(c!=null&&c.length>0){this.isHtmlLabel(a)||
-(c=c.replace(/\n/g,"<br>"));e=mxUtils.getSizeForString(c,e,d[mxConstants.STYLE_FONTFAMILY]);a=e.width+f;b=e.height+b;if(!mxUtils.getValue(d,mxConstants.STYLE_HORIZONTAL,true)){d=b;b=a;a=d}if(this.gridEnabled){a=this.snap(a+this.gridSize/2);b=this.snap(b+this.gridSize/2)}b=new mxRectangle(0,0,a,b)}else{d=4*this.gridSize;b=new mxRectangle(0,0,d,d)}}}return b};
-mxGraph.prototype.handleGesture=function(a,b){if(Math.abs(1-b.scale)>0.2){var c=this.view.scale,d=this.view.translate,e=a.width*b.scale,f=a.height*b.scale,g=a.y-(f-a.height)/2,c=new mxRectangle(this.snap((a.x-(e-a.width)/2)/c)-d.x,this.snap(g/c)-d.y,this.snap(e/c),this.snap(f/c));this.resizeCell(a.cell,c)}};mxGraph.prototype.resizeCell=function(a,b){return this.resizeCells([a],[b])[0]};
-mxGraph.prototype.resizeCells=function(a,b){this.model.beginUpdate();try{this.cellsResized(a,b);this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,"cells",a,"bounds",b))}finally{this.model.endUpdate()}return a};
-mxGraph.prototype.cellsResized=function(a,b){if(a!=null&&b!=null&&a.length==b.length){this.model.beginUpdate();try{for(var c=0;c<a.length;c++){var d=b[c],e=this.model.getGeometry(a[c]);if(e!=null&&(e.x!=d.x||e.y!=d.y||e.width!=d.width||e.height!=d.height)){e=e.clone();if(e.relative){var f=e.offset;if(f!=null){f.x=f.x+(d.x-e.x);f.y=f.y+(d.y-e.y)}}else{e.x=d.x;e.y=d.y}e.width=d.width;e.height=d.height;if(!e.relative&&this.model.isVertex(a[c])&&!this.isAllowNegativeCoordinates()){e.x=Math.max(0,e.x);
-e.y=Math.max(0,e.y)}this.model.setGeometry(a[c],e);this.isExtendParent(a[c])&&this.extendParent(a[c])}}this.resetEdgesOnResize&&this.resetEdges(a);this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED,"cells",a,"bounds",b))}finally{this.model.endUpdate()}}};
-mxGraph.prototype.extendParent=function(a){if(a!=null){var b=this.model.getParent(a),c=this.model.getGeometry(b);if(b!=null&&c!=null&&!this.isCellCollapsed(b)){a=this.model.getGeometry(a);if(a!=null&&(c.width<a.x+a.width||c.height<a.y+a.height)){c=c.clone();c.width=Math.max(c.width,a.x+a.width);c.height=Math.max(c.height,a.y+a.height);this.cellsResized([b],[c])}}}};mxGraph.prototype.importCells=function(a,b,c,d,e){return this.moveCells(a,b,c,true,d,e)};
-mxGraph.prototype.moveCells=function(a,b,c,d,e,f){b=b!=null?b:0;c=c!=null?c:0;d=d!=null?d:false;if(a!=null&&(b!=0||c!=0||d||e!=null)){this.model.beginUpdate();try{if(d){a=this.cloneCells(a,this.isCloneInvalidEdges());e==null&&(e=this.getDefaultParent())}var g=this.isAllowNegativeCoordinates();e!=null&&this.setAllowNegativeCoordinates(true);this.cellsMoved(a,b,c,!d&&this.isDisconnectOnMove()&&this.isAllowDanglingEdges(),e==null);this.setAllowNegativeCoordinates(g);if(e!=null){var h=this.model.getChildCount(e);
-this.cellsAdded(a,e,h,null,null,true)}this.fireEvent(new mxEventObject(mxEvent.MOVE_CELLS,"cells",a,"dx",b,"dy",c,"clone",d,"target",e,"event",f))}finally{this.model.endUpdate()}}return a};
-mxGraph.prototype.cellsMoved=function(a,b,c,d,e){if(a!=null&&(b!=0||c!=0)){this.model.beginUpdate();try{d&&this.disconnectGraph(a);for(var f=0;f<a.length;f++){this.translateCell(a[f],b,c);e&&this.constrainChild(a[f])}this.resetEdgesOnMove&&this.resetEdges(a);this.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED,"cells",a,"dx",c,"dy",c,"disconnect",d))}finally{this.model.endUpdate()}}};
-mxGraph.prototype.translateCell=function(a,b,c){var d=this.model.getGeometry(a);if(d!=null){d=d.clone();d.translate(b,c);if(!d.relative&&this.model.isVertex(a)&&!this.isAllowNegativeCoordinates()){d.x=Math.max(0,d.x);d.y=Math.max(0,d.y)}if(d.relative&&!this.model.isEdge(a))if(d.offset==null)d.offset=new mxPoint(b,c);else{d.offset.x=d.offset.x+b;d.offset.y=d.offset.y+c}this.model.setGeometry(a,d)}};
-mxGraph.prototype.getCellContainmentArea=function(a){if(a!=null&&!this.model.isEdge(a)){var b=this.model.getParent(a);if(b==this.getDefaultParent()||b==this.getCurrentRoot())return this.getMaximumGraphBounds();if(b!=null&&b!=this.getDefaultParent()){var c=this.model.getGeometry(b);if(c!=null){var d=a=0,e=c.width,c=c.height;if(this.isSwimlane(b)){b=this.getStartSize(b);a=b.width;e=e-b.width;d=b.height;c=c-b.height}return new mxRectangle(a,d,e,c)}}}return null};
-mxGraph.prototype.getMaximumGraphBounds=function(){return this.maximumGraphBounds};
-mxGraph.prototype.constrainChild=function(a){if(a!=null){var b=this.model.getGeometry(a),c=this.isConstrainChild(a)?this.getCellContainmentArea(a):this.getMaximumGraphBounds();if(b!=null&&c!=null&&!b.relative&&(b.x<c.x||b.y<c.y||c.width<b.x+b.width||c.height<b.y+b.height)){a=this.getOverlap(a);if(c.width>0)b.x=Math.min(b.x,c.x+c.width-(1-a)*b.width);if(c.height>0)b.y=Math.min(b.y,c.y+c.height-(1-a)*b.height);b.x=Math.max(b.x,c.x-b.width*a);b.y=Math.max(b.y,c.y-b.height*a)}}};
-mxGraph.prototype.resetEdges=function(a){if(a!=null){for(var b={},c=0;c<a.length;c++){var d=mxCellPath.create(a[c]);b[d]=a[c]}this.model.beginUpdate();try{for(c=0;c<a.length;c++){var e=this.model.getEdges(a[c]);if(e!=null)for(d=0;d<e.length;d++){var f=this.view.getState(e[d]),g=f!=null?f.getVisibleTerminal(true):this.view.getVisibleTerminal(e[d],true),h=f!=null?f.getVisibleTerminal(false):this.view.getVisibleTerminal(e[d],false),k=mxCellPath.create(g),i=mxCellPath.create(h);(b[k]==null||b[i]==null)&&
-this.resetEdge(e[d])}this.resetEdges(this.model.getChildren(a[c]))}}finally{this.model.endUpdate()}}};mxGraph.prototype.resetEdge=function(a){var b=this.model.getGeometry(a);if(b!=null&&b.points!=null&&b.points.length>0){b=b.clone();b.points=[];this.model.setGeometry(a,b)}return a};mxGraph.prototype.getAllConnectionConstraints=function(a){return a!=null&&(a.shape!=null&&a.shape instanceof mxStencilShape)&&a.shape.stencil!=null?a.shape.stencil.constraints:null};
-mxGraph.prototype.getConnectionConstraint=function(a,b,c){var b=null,d=a.style[c?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X];if(d!=null){var e=a.style[c?mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y];e!=null&&(b=new mxPoint(parseFloat(d),parseFloat(e)))}d=false;b!=null&&(d=mxUtils.getValue(a.style,c?mxConstants.STYLE_EXIT_PERIMETER:mxConstants.STYLE_ENTRY_PERIMETER,true));return new mxConnectionConstraint(b,d)};
-mxGraph.prototype.setConnectionConstraint=function(a,b,c,d){if(d!=null){this.model.beginUpdate();try{if(d==null||d.point==null){this.setCellStyles(c?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X,null,[a]);this.setCellStyles(c?mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y,null,[a]);this.setCellStyles(c?mxConstants.STYLE_EXIT_PERIMETER:mxConstants.STYLE_ENTRY_PERIMETER,null,[a])}else if(d.point!=null){this.setCellStyles(c?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X,d.point.x,[a]);this.setCellStyles(c?
-mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y,d.point.y,[a]);d.perimeter?this.setCellStyles(c?mxConstants.STYLE_EXIT_PERIMETER:mxConstants.STYLE_ENTRY_PERIMETER,null,[a]):this.setCellStyles(c?mxConstants.STYLE_EXIT_PERIMETER:mxConstants.STYLE_ENTRY_PERIMETER,"0",[a])}}finally{this.model.endUpdate()}}};
-mxGraph.prototype.getConnectionPoint=function(a,b){var c=null;if(a!=null){var d=this.view.getPerimeterBounds(a),e=new mxPoint(d.getCenterX(),d.getCenterY()),f=a.style[mxConstants.STYLE_DIRECTION],g=0;if(f!=null){f=="north"?g=g+270:f=="west"?g=g+180:f=="south"&&(g=g+90);if(f=="north"||f=="south"){d.x=d.x+(d.width/2-d.height/2);d.y=d.y+(d.height/2-d.width/2);var h=d.width;d.width=d.height;d.height=h}}if(b.point!=null){var k=c=1,i=0,l=0;if(a.shape instanceof mxStencilShape){var m=a.style[mxConstants.STYLE_STENCIL_FLIPH],
-n=a.style[mxConstants.STYLE_STENCIL_FLIPV];if(f=="north"||f=="south"){h=m;m=n;n=h}if(m){c=-1;i=-d.width}if(n){k=-1;l=-d.height}}c=new mxPoint(d.x+b.point.x*d.width*c-i,d.y+b.point.y*d.height*k-l)}f=a.style[mxConstants.STYLE_ROTATION]||0;if(b.perimeter){if(g!=0&&c!=null){h=d=0;g==90?h=1:g==180?d=-1:f==270&&(h=-1);c=mxUtils.getRotatedPoint(c,d,h,e)}c!=null&&b.perimeter&&(c=this.view.getPerimeterPoint(a,c,false))}else f=f+g;if(f!=0&&c!=null){g=mxUtils.toRadians(f);d=Math.cos(g);h=Math.sin(g);c=mxUtils.getRotatedPoint(c,
-d,h,e)}}return c};mxGraph.prototype.connectCell=function(a,b,c,d){this.model.beginUpdate();try{var e=this.model.getTerminal(a,c);this.cellConnected(a,b,c,d);this.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL,"edge",a,"terminal",b,"source",c,"previous",e))}finally{this.model.endUpdate()}return a};
-mxGraph.prototype.cellConnected=function(a,b,c,d){if(a!=null){this.model.beginUpdate();try{var e=this.model.getTerminal(a,c);this.setConnectionConstraint(a,b,c,d);if(this.isPortsEnabled()){d=null;if(this.isPort(b)){d=b.getId();b=this.getTerminalForPort(b,c)}this.setCellStyles(c?mxConstants.STYLE_SOURCE_PORT:mxConstants.STYLE_TARGET_PORT,d,[a])}this.model.setTerminal(a,b,c);this.resetEdgesOnConnect&&this.resetEdge(a);this.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED,"edge",a,"terminal",b,"source",
-c,"previous",e))}finally{this.model.endUpdate()}}};
-mxGraph.prototype.disconnectGraph=function(a){if(a!=null){this.model.beginUpdate();try{for(var b=this.view.scale,c=this.view.translate,d={},e=0;e<a.length;e++){var f=mxCellPath.create(a[e]);d[f]=a[e]}for(e=0;e<a.length;e++)if(this.model.isEdge(a[e])){var g=this.model.getGeometry(a[e]);if(g!=null){var h=this.view.getState(a[e]),k=this.view.getState(this.model.getParent(a[e]));if(h!=null&&k!=null){var g=g.clone(),i=-k.origin.x,l=-k.origin.y,m=h.absolutePoints,n=this.model.getTerminal(a[e],true);if(n!=
-null&&this.isCellDisconnectable(a[e],n,true)){for(var o=mxCellPath.create(n);n!=null&&d[o]==null;){n=this.model.getParent(n);o=mxCellPath.create(n)}if(n==null){g.setTerminalPoint(new mxPoint(m[0].x/b-c.x+i,m[0].y/b-c.y+l),true);this.model.setTerminal(a[e],null,true)}}var p=this.model.getTerminal(a[e],false);if(p!=null&&this.isCellDisconnectable(a[e],p,false)){for(var q=mxCellPath.create(p);p!=null&&d[q]==null;){p=this.model.getParent(p);q=mxCellPath.create(p)}if(p==null){var t=m.length-1;g.setTerminalPoint(new mxPoint(m[t].x/
-b-c.x+i,m[t].y/b-c.y+l),false);this.model.setTerminal(a[e],null,false)}}this.model.setGeometry(a[e],g)}}}}finally{this.model.endUpdate()}}};mxGraph.prototype.getCurrentRoot=function(){return this.view.currentRoot};mxGraph.prototype.getTranslateForRoot=function(){return null};mxGraph.prototype.isPort=function(){return false};mxGraph.prototype.getTerminalForPort=function(a){return this.model.getParent(a)};mxGraph.prototype.getChildOffsetForCell=function(){return null};
-mxGraph.prototype.enterGroup=function(a){a=a||this.getSelectionCell();if(a!=null&&this.isValidRoot(a)){this.view.setCurrentRoot(a);this.clearSelection()}};mxGraph.prototype.exitGroup=function(){var a=this.model.getRoot(),b=this.getCurrentRoot();if(b!=null){for(var c=this.model.getParent(b);c!=a&&!this.isValidRoot(c)&&this.model.getParent(c)!=a;)c=this.model.getParent(c);c==a||this.model.getParent(c)==a?this.view.setCurrentRoot(null):this.view.setCurrentRoot(c);this.view.getState(b)!=null&&this.setSelectionCell(b)}};
-mxGraph.prototype.home=function(){var a=this.getCurrentRoot();if(a!=null){this.view.setCurrentRoot(null);this.view.getState(a)!=null&&this.setSelectionCell(a)}};mxGraph.prototype.isValidRoot=function(a){return a!=null};mxGraph.prototype.getGraphBounds=function(){return this.view.getGraphBounds()};
-mxGraph.prototype.getCellBounds=function(a,b,c){var d=[a];b&&(d=d.concat(this.model.getEdges(a)));d=this.view.getBounds(d);if(c)for(var c=this.model.getChildCount(a),e=0;e<c;e++){var f=this.getCellBounds(this.model.getChildAt(a,e),b,true);d!=null?d.add(f):d=f}return d};
-mxGraph.prototype.getBoundingBoxFromGeometry=function(a,b){var b=b!=null?b:false,c=null;if(a!=null)for(var d=0;d<a.length;d++)if(b||this.model.isVertex(a[d])){var e=this.getCellGeometry(a[d]);if(e!=null){var f=e.points;if(f!=null&&f.length>0){for(var g=new mxRectangle(f[0].x,f[0].y,0,0),h=function(a){a!=null&&g.add(new mxRectangle(a.x,a.y,0,0))},k=1;k<f.length;k++)h(f[k]);h(e.getTerminalPoint(true));h(e.getTerminalPoint(false))}c==null?c=new mxRectangle(e.x,e.y,e.width,e.height):c.add(e)}}return c};
-mxGraph.prototype.refresh=function(a){this.view.clear(a,a==null);this.view.validate();this.sizeDidChange();this.fireEvent(new mxEventObject(mxEvent.REFRESH))};mxGraph.prototype.snap=function(a){this.gridEnabled&&(a=Math.round(a/this.gridSize)*this.gridSize);return a};
-mxGraph.prototype.panGraph=function(a,b){if(this.useScrollbarsForPanning&&mxUtils.hasScrollbars(this.container)){this.container.scrollLeft=-a;this.container.scrollTop=-b}else{var c=this.view.getCanvas();if(this.dialect==mxConstants.DIALECT_SVG)if(a==0&&b==0){mxClient.IS_IE?c.setAttribute("transform","translate("+a+","+b+")"):c.removeAttribute("transform");if(this.shiftPreview1!=null){for(var d=this.shiftPreview1.firstChild;d!=null;){var e=d.nextSibling;this.container.appendChild(d);d=e}this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);
-this.shiftPreview1=null;this.container.appendChild(c.parentNode);for(d=this.shiftPreview2.firstChild;d!=null;){e=d.nextSibling;this.container.appendChild(d);d=e}this.shiftPreview2.parentNode.removeChild(this.shiftPreview2);this.shiftPreview2=null}}else{c.setAttribute("transform","translate("+a+","+b+")");if(this.shiftPreview1==null){this.shiftPreview1=document.createElement("div");this.shiftPreview1.style.position="absolute";this.shiftPreview1.style.overflow="visible";this.shiftPreview2=document.createElement("div");
-this.shiftPreview2.style.position="absolute";this.shiftPreview2.style.overflow="visible";for(var f=this.shiftPreview1,d=this.container.firstChild;d!=null;){e=d.nextSibling;d!=c.parentNode?f.appendChild(d):f=this.shiftPreview2;d=e}this.container.insertBefore(this.shiftPreview1,c.parentNode);this.container.appendChild(this.shiftPreview2)}this.shiftPreview1.style.left=a+"px";this.shiftPreview1.style.top=b+"px";this.shiftPreview2.style.left=a+"px";this.shiftPreview2.style.top=b+"px"}else{c.style.left=
-a+"px";c.style.top=b+"px"}this.panDx=a;this.panDy=b;this.fireEvent(new mxEventObject(mxEvent.PAN))}};mxGraph.prototype.zoomIn=function(){this.zoom(this.zoomFactor)};mxGraph.prototype.zoomOut=function(){this.zoom(1/this.zoomFactor)};mxGraph.prototype.zoomActual=function(){if(this.view.scale==1)this.view.setTranslate(0,0);else{this.view.translate.x=0;this.view.translate.y=0;this.view.setScale(1)}};mxGraph.prototype.zoomTo=function(a,b){this.zoom(a/this.view.scale,b)};
-mxGraph.prototype.zoom=function(a,b){var b=b!=null?b:this.centerZoom,c=this.view.scale*a,d=this.view.getState(this.getSelectionCell());if(this.keepSelectionVisibleOnZoom&&d!=null){d=new mxRectangle(d.x*a,d.y*a,d.width*a,d.height*a);this.view.scale=c;if(!this.scrollRectToVisible(d)){this.view.revalidate();this.view.setScale(c)}}else if(b&&!mxUtils.hasScrollbars(this.container)){var d=this.container.offsetWidth,e=this.container.offsetHeight;if(a>1)var f=(a-1)/(c*2),d=d*-f,e=e*-f;else{f=(1/a-1)/(this.view.scale*
-2);d=d*f;e=e*f}this.view.scaleAndTranslate(c,this.view.translate.x+d,this.view.translate.y+e)}else{this.view.setScale(c);if(mxUtils.hasScrollbars(this.container)){e=d=0;if(b){d=this.container.offsetWidth*(a-1)/2;e=this.container.offsetHeight*(a-1)/2}this.container.scrollLeft=Math.round(this.container.scrollLeft*a+d);this.container.scrollTop=Math.round(this.container.scrollTop*a+e)}}};
-mxGraph.prototype.zoomToRect=function(a){var b=this.container.clientWidth/a.width/(this.container.clientHeight/a.height);a.x=Math.max(0,a.x);a.y=Math.max(0,a.y);var c=Math.min(this.container.scrollWidth,a.x+a.width),d=Math.min(this.container.scrollHeight,a.y+a.height);a.width=c-a.x;a.height=d-a.y;if(b<1){b=a.height/b;c=(b-a.height)/2;a.height=b;b=Math.min(a.y,c);a.y=a.y-b;d=Math.min(this.container.scrollHeight,a.y+a.height);a.height=d-a.y}else{b=a.width*b;c=(b-a.width)/2;a.width=b;b=Math.min(a.x,
-c);a.x=a.x-b;c=Math.min(this.container.scrollWidth,a.x+a.width);a.width=c-a.x}b=this.container.clientWidth/a.width;if(mxUtils.hasScrollbars(this.container)){this.view.setScale(b);this.container.scrollLeft=Math.round(a.x*b);this.container.scrollTop=Math.round(a.y*b)}else this.view.scaleAndTranslate(b,-a.x,-a.y)};
-mxGraph.prototype.fit=function(a,b){if(this.container!=null){var a=a!=null?a:0,b=b!=null?b:false,c=this.container.clientWidth,d=this.container.clientHeight,e=this.view.getGraphBounds();if(b&&e.x!=null&&e.y!=null){e.width=e.width+e.x;e.height=e.height+e.y;e.x=0;e.y=0}var f=this.view.scale,g=e.width/f,h=e.height/f;if(this.backgroundImage!=null){g=Math.max(g,this.backgroundImage.width-e.x/f);h=Math.max(h,this.backgroundImage.height-e.y/f)}var k=b?a:2*a,c=Math.floor(Math.min(c/(g+k),d/(h+k))*100)/100;
-this.minFitScale!=null&&(c=Math.max(c,this.minFitScale));this.maxFitScale!=null&&(c=Math.min(c,this.maxFitScale));if(b)this.view.scale!=c&&this.view.setScale(c);else if(mxUtils.hasScrollbars(this.container)){this.view.setScale(c);if(e.x!=null)this.container.scrollLeft=Math.round(e.x/f)*c-a-Math.max(0,(this.container.clientWidth-g*c)/2);if(e.y!=null)this.container.scrollTop=Math.round(e.y/f)*c-a-Math.max(0,(this.container.clientHeight-h*c)/2)}else this.view.scaleAndTranslate(c,e.x!=null?Math.floor(this.view.translate.x-
-e.x/f+a+1):a,e.y!=null?Math.floor(this.view.translate.y-e.y/f+a+1):a)}return this.view.scale};
-mxGraph.prototype.scrollCellToVisible=function(a,b){var c=-this.view.translate.x,d=-this.view.translate.y,e=this.view.getState(a);if(e!=null){c=new mxRectangle(c+e.x,d+e.y,e.width,e.height);if(b&&this.container!=null){d=this.container.clientWidth;e=this.container.clientHeight;c.x=c.getCenterX()-d/2;c.width=d;c.y=c.getCenterY()-e/2;c.height=e}this.scrollRectToVisible(c)&&this.view.setTranslate(this.view.translate.x,this.view.translate.y)}};
-mxGraph.prototype.scrollRectToVisible=function(a){var b=false;if(a!=null){var c=this.container.offsetWidth,d=this.container.offsetHeight,e=Math.min(c,a.width),f=Math.min(d,a.height);if(mxUtils.hasScrollbars(this.container)){c=this.container;a.x=a.x+this.view.translate.x;a.y=a.y+this.view.translate.y;var g=c.scrollLeft-a.x,d=Math.max(g-c.scrollLeft,0);if(g>0)c.scrollLeft=c.scrollLeft-(g+2);else{g=a.x+e-c.scrollLeft-c.clientWidth;if(g>0)c.scrollLeft=c.scrollLeft+(g+2)}e=c.scrollTop-a.y;g=Math.max(0,
-e-c.scrollTop);if(e>0)c.scrollTop=c.scrollTop-(e+2);else{e=a.y+f-c.scrollTop-c.clientHeight;if(e>0)c.scrollTop=c.scrollTop+(e+2)}!this.useScrollbarsForPanning&&(d!=0||g!=0)&&this.view.setTranslate(d,g)}else{var g=-this.view.translate.x,h=-this.view.translate.y,k=this.view.scale;if(a.x+e>g+c){this.view.translate.x=this.view.translate.x-(a.x+e-c-g)/k;b=true}if(a.y+f>h+d){this.view.translate.y=this.view.translate.y-(a.y+f-d-h)/k;b=true}if(a.x<g){this.view.translate.x=this.view.translate.x+(g-a.x)/k;
-b=true}if(a.y<h){this.view.translate.y=this.view.translate.y+(h-a.y)/k;b=true}if(b){this.view.refresh();this.selectionCellsHandler!=null&&this.selectionCellsHandler.refresh()}}}return b};mxGraph.prototype.getCellGeometry=function(a){return this.model.getGeometry(a)};mxGraph.prototype.isCellVisible=function(a){return this.model.isVisible(a)};mxGraph.prototype.isCellCollapsed=function(a){return this.model.isCollapsed(a)};mxGraph.prototype.isCellConnectable=function(a){return this.model.isConnectable(a)};
-mxGraph.prototype.isOrthogonal=function(a){var b=a.style[mxConstants.STYLE_ORTHOGONAL];if(b!=null)return b;a=this.view.getEdgeStyle(a);return a==mxEdgeStyle.SegmentConnector||a==mxEdgeStyle.ElbowConnector||a==mxEdgeStyle.SideToSide||a==mxEdgeStyle.TopToBottom||a==mxEdgeStyle.EntityRelation||a==mxEdgeStyle.OrthConnector};mxGraph.prototype.isLoop=function(a){var b=a.getVisibleTerminalState(true),a=a.getVisibleTerminalState(false);return b!=null&&b==a};mxGraph.prototype.isCloneEvent=function(a){return mxEvent.isControlDown(a)};
-mxGraph.prototype.isToggleEvent=function(a){return mxClient.IS_MAC?mxEvent.isMetaDown(a):mxEvent.isControlDown(a)};mxGraph.prototype.isGridEnabledEvent=function(a){return a!=null&&!mxEvent.isAltDown(a)};mxGraph.prototype.isConstrainedEvent=function(a){return mxEvent.isShiftDown(a)};mxGraph.prototype.isForceMarqueeEvent=function(a){return mxEvent.isAltDown(a)};mxGraph.prototype.validationAlert=function(a){mxUtils.alert(a)};
-mxGraph.prototype.isEdgeValid=function(a,b,c){return this.getEdgeValidationError(a,b,c)==null};
-mxGraph.prototype.getEdgeValidationError=function(a,b,c){if(a!=null&&!this.isAllowDanglingEdges()&&(b==null||c==null))return"";if(a!=null&&this.model.getTerminal(a,true)==null&&this.model.getTerminal(a,false)==null)return null;if(!this.allowLoops&&b==c&&b!=null||!this.isValidConnection(b,c))return"";if(b!=null&&c!=null){var d="";if(!this.multigraph){var e=this.model.getEdgesBetween(b,c,true);if(e.length>1||e.length==1&&e[0]!=a)d=d+((mxResources.get(this.alreadyConnectedResource)||this.alreadyConnectedResource)+
-"\n")}var e=this.model.getDirectedEdgeCount(b,true,a),f=this.model.getDirectedEdgeCount(c,false,a);if(this.multiplicities!=null)for(var g=0;g<this.multiplicities.length;g++){var h=this.multiplicities[g].check(this,a,b,c,e,f);h!=null&&(d=d+h)}h=this.validateEdge(a,b,c);h!=null&&(d=d+h);return d.length>0?d:null}return this.allowDanglingEdges?null:""};mxGraph.prototype.validateEdge=function(){return null};
-mxGraph.prototype.validateGraph=function(a,b){for(var a=a!=null?a:this.model.getRoot(),b=b!=null?b:{},c=true,d=this.model.getChildCount(a),e=0;e<d;e++){var f=this.model.getChildAt(a,e),g=b;this.isValidRoot(f)&&(g={});g=this.validateGraph(f,g);g!=null?this.setCellWarning(f,g.replace(/\n/g,"<br>")):this.setCellWarning(f,null);c=c&&g==null}d="";this.isCellCollapsed(a)&&!c&&(d=d+((mxResources.get(this.containsValidationErrorsResource)||this.containsValidationErrorsResource)+"\n"));d=this.model.isEdge(a)?
-d+(this.getEdgeValidationError(a,this.model.getTerminal(a,true),this.model.getTerminal(a,false))||""):d+(this.getCellValidationError(a)||"");e=this.validateCell(a,b);e!=null&&(d=d+e);this.model.getParent(a)==null&&this.view.validate();return d.length>0||!c?d:null};
-mxGraph.prototype.getCellValidationError=function(a){var b=this.model.getDirectedEdgeCount(a,true),c=this.model.getDirectedEdgeCount(a,false),a=this.model.getValue(a),d="";if(this.multiplicities!=null)for(var e=0;e<this.multiplicities.length;e++){var f=this.multiplicities[e];if(f.source&&mxUtils.isNode(a,f.type,f.attr,f.value)&&(f.max==0&&b>0||f.min==1&&b==0||f.max==1&&b>1))d=d+(f.countError+"\n");else if(!f.source&&mxUtils.isNode(a,f.type,f.attr,f.value)&&(f.max==0&&c>0||f.min==1&&c==0||f.max==1&&
-c>1))d=d+(f.countError+"\n")}return d.length>0?d:null};mxGraph.prototype.validateCell=function(){return null};mxGraph.prototype.getBackgroundImage=function(){return this.backgroundImage};mxGraph.prototype.setBackgroundImage=function(a){this.backgroundImage=a};mxGraph.prototype.getFoldingImage=function(a){if(a!=null&&this.foldingEnabled&&!this.getModel().isEdge(a.cell)){var b=this.isCellCollapsed(a.cell);if(this.isCellFoldable(a.cell,!b))return b?this.collapsedImage:this.expandedImage}return null};
-mxGraph.prototype.convertValueToString=function(a){a=this.model.getValue(a);if(a!=null){if(mxUtils.isNode(a))return a.nodeName;if(typeof a.toString=="function")return a.toString()}return""};mxGraph.prototype.getLabel=function(a){var b="";if(this.labelsVisible&&a!=null){var c=this.view.getState(a),c=c!=null?c.style:this.getCellStyle(a);mxUtils.getValue(c,mxConstants.STYLE_NOLABEL,false)||(b=this.convertValueToString(a))}return b};mxGraph.prototype.isHtmlLabel=function(){return this.isHtmlLabels()};
-mxGraph.prototype.isHtmlLabels=function(){return this.htmlLabels};mxGraph.prototype.setHtmlLabels=function(a){this.htmlLabels=a};mxGraph.prototype.isWrapping=function(a){var b=this.view.getState(a),a=b!=null?b.style:this.getCellStyle(a);return a!=null?a[mxConstants.STYLE_WHITE_SPACE]=="wrap":false};mxGraph.prototype.isLabelClipped=function(a){var b=this.view.getState(a),a=b!=null?b.style:this.getCellStyle(a);return a!=null?a[mxConstants.STYLE_OVERFLOW]=="hidden":false};
-mxGraph.prototype.getTooltip=function(a,b){var c=null;if(a!=null){if(a.control!=null&&(b==a.control.node||b.parentNode==a.control.node)){c=this.collapseExpandResource;c=mxResources.get(c)||c}c==null&&a.overlays!=null&&a.overlays.visit(function(a,d){if(c==null&&(b==d.node||b.parentNode==d.node))c=d.overlay.toString()});if(c==null){var d=this.selectionCellsHandler.getHandler(a.cell);d!=null&&typeof d.getTooltipForNode=="function"&&(c=d.getTooltipForNode(b))}c==null&&(c=this.getTooltipForCell(a.cell))}return c};
-mxGraph.prototype.getTooltipForCell=function(a){var b=null;return b=a!=null&&a.getTooltip!=null?a.getTooltip():this.convertValueToString(a)};mxGraph.prototype.getCursorForCell=function(){return null};
-mxGraph.prototype.getStartSize=function(a){var b=new mxRectangle,c=this.view.getState(a),a=c!=null?c.style:this.getCellStyle(a);if(a!=null){c=parseInt(mxUtils.getValue(a,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE));mxUtils.getValue(a,mxConstants.STYLE_HORIZONTAL,true)?b.height=c:b.width=c}return b};mxGraph.prototype.getImage=function(a){return a!=null&&a.style!=null?a.style[mxConstants.STYLE_IMAGE]:null};
-mxGraph.prototype.getVerticalAlign=function(a){return a!=null&&a.style!=null?a.style[mxConstants.STYLE_VERTICAL_ALIGN]||mxConstants.ALIGN_MIDDLE:null};mxGraph.prototype.getIndicatorColor=function(a){return a!=null&&a.style!=null?a.style[mxConstants.STYLE_INDICATOR_COLOR]:null};mxGraph.prototype.getIndicatorGradientColor=function(a){return a!=null&&a.style!=null?a.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR]:null};
-mxGraph.prototype.getIndicatorShape=function(a){return a!=null&&a.style!=null?a.style[mxConstants.STYLE_INDICATOR_SHAPE]:null};mxGraph.prototype.getIndicatorImage=function(a){return a!=null&&a.style!=null?a.style[mxConstants.STYLE_INDICATOR_IMAGE]:null};mxGraph.prototype.getBorder=function(){return this.border};mxGraph.prototype.setBorder=function(a){this.border=a};
-mxGraph.prototype.isSwimlane=function(a){if(a!=null&&this.model.getParent(a)!=this.model.getRoot()){var b=this.view.getState(a),b=b!=null?b.style:this.getCellStyle(a);if(b!=null&&!this.model.isEdge(a))return b[mxConstants.STYLE_SHAPE]==mxConstants.SHAPE_SWIMLANE}return false};mxGraph.prototype.isResizeContainer=function(){return this.resizeContainer};mxGraph.prototype.setResizeContainer=function(a){this.resizeContainer=a};mxGraph.prototype.isEnabled=function(){return this.enabled};
-mxGraph.prototype.setEnabled=function(a){this.enabled=a};mxGraph.prototype.isEscapeEnabled=function(){return this.escapeEnabled};mxGraph.prototype.setEscapeEnabled=function(a){this.escapeEnabled=a};mxGraph.prototype.isInvokesStopCellEditing=function(){return this.invokesStopCellEditing};mxGraph.prototype.setInvokesStopCellEditing=function(a){this.invokesStopCellEditing=a};mxGraph.prototype.isEnterStopsCellEditing=function(){return this.enterStopsCellEditing};
-mxGraph.prototype.setEnterStopsCellEditing=function(a){this.enterStopsCellEditing=a};mxGraph.prototype.isCellLocked=function(a){var b=this.model.getGeometry(a);return this.isCellsLocked()||b!=null&&this.model.isVertex(a)&&b.relative};mxGraph.prototype.isCellsLocked=function(){return this.cellsLocked};mxGraph.prototype.setCellsLocked=function(a){this.cellsLocked=a};mxGraph.prototype.getCloneableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.isCellCloneable(a)}))};
-mxGraph.prototype.isCellCloneable=function(a){var b=this.view.getState(a),a=b!=null?b.style:this.getCellStyle(a);return this.isCellsCloneable()&&a[mxConstants.STYLE_CLONEABLE]!=0};mxGraph.prototype.isCellsCloneable=function(){return this.cellsCloneable};mxGraph.prototype.setCellsCloneable=function(a){this.cellsCloneable=a};mxGraph.prototype.getExportableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.canExportCell(a)}))};
-mxGraph.prototype.canExportCell=function(){return this.exportEnabled};mxGraph.prototype.getImportableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.canImportCell(a)}))};mxGraph.prototype.canImportCell=function(){return this.importEnabled};mxGraph.prototype.isCellSelectable=function(){return this.isCellsSelectable()};mxGraph.prototype.isCellsSelectable=function(){return this.cellsSelectable};
-mxGraph.prototype.setCellsSelectable=function(a){this.cellsSelectable=a};mxGraph.prototype.getDeletableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.isCellDeletable(a)}))};mxGraph.prototype.isCellDeletable=function(a){var b=this.view.getState(a),a=b!=null?b.style:this.getCellStyle(a);return this.isCellsDeletable()&&a[mxConstants.STYLE_DELETABLE]!=0};mxGraph.prototype.isCellsDeletable=function(){return this.cellsDeletable};
-mxGraph.prototype.setCellsDeletable=function(a){this.cellsDeletable=a};mxGraph.prototype.isLabelMovable=function(a){return!this.isCellLocked(a)&&(this.model.isEdge(a)&&this.edgeLabelsMovable||this.model.isVertex(a)&&this.vertexLabelsMovable)};mxGraph.prototype.getMovableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.isCellMovable(a)}))};
-mxGraph.prototype.isCellMovable=function(a){var b=this.view.getState(a),b=b!=null?b.style:this.getCellStyle(a);return this.isCellsMovable()&&!this.isCellLocked(a)&&b[mxConstants.STYLE_MOVABLE]!=0};mxGraph.prototype.isCellsMovable=function(){return this.cellsMovable};mxGraph.prototype.setCellsMovable=function(a){this.cellsMovable=a};mxGraph.prototype.isGridEnabled=function(){return this.gridEnabled};mxGraph.prototype.setGridEnabled=function(a){this.gridEnabled=a};mxGraph.prototype.isPortsEnabled=function(){return this.portsEnabled};
-mxGraph.prototype.setPortsEnabled=function(a){this.portsEnabled=a};mxGraph.prototype.getGridSize=function(){return this.gridSize};mxGraph.prototype.setGridSize=function(a){this.gridSize=a};mxGraph.prototype.getTolerance=function(){return this.tolerance};mxGraph.prototype.setTolerance=function(a){this.tolerance=a};mxGraph.prototype.isVertexLabelsMovable=function(){return this.vertexLabelsMovable};mxGraph.prototype.setVertexLabelsMovable=function(a){this.vertexLabelsMovable=a};
-mxGraph.prototype.isEdgeLabelsMovable=function(){return this.edgeLabelsMovable};mxGraph.prototype.setEdgeLabelsMovable=function(a){this.edgeLabelsMovable=a};mxGraph.prototype.isSwimlaneNesting=function(){return this.swimlaneNesting};mxGraph.prototype.setSwimlaneNesting=function(a){this.swimlaneNesting=a};mxGraph.prototype.isSwimlaneSelectionEnabled=function(){return this.swimlaneSelectionEnabled};mxGraph.prototype.setSwimlaneSelectionEnabled=function(a){this.swimlaneSelectionEnabled=a};
-mxGraph.prototype.isMultigraph=function(){return this.multigraph};mxGraph.prototype.setMultigraph=function(a){this.multigraph=a};mxGraph.prototype.isAllowLoops=function(){return this.allowLoops};mxGraph.prototype.setAllowDanglingEdges=function(a){this.allowDanglingEdges=a};mxGraph.prototype.isAllowDanglingEdges=function(){return this.allowDanglingEdges};mxGraph.prototype.setConnectableEdges=function(a){this.connectableEdges=a};mxGraph.prototype.isConnectableEdges=function(){return this.connectableEdges};
-mxGraph.prototype.setCloneInvalidEdges=function(a){this.cloneInvalidEdges=a};mxGraph.prototype.isCloneInvalidEdges=function(){return this.cloneInvalidEdges};mxGraph.prototype.setAllowLoops=function(a){this.allowLoops=a};mxGraph.prototype.isDisconnectOnMove=function(){return this.disconnectOnMove};mxGraph.prototype.setDisconnectOnMove=function(a){this.disconnectOnMove=a};mxGraph.prototype.isDropEnabled=function(){return this.dropEnabled};
-mxGraph.prototype.setDropEnabled=function(a){this.dropEnabled=a};mxGraph.prototype.isSplitEnabled=function(){return this.splitEnabled};mxGraph.prototype.setSplitEnabled=function(a){this.splitEnabled=a};mxGraph.prototype.isCellResizable=function(a){var b=this.view.getState(a),b=b!=null?b.style:this.getCellStyle(a);return this.isCellsResizable()&&!this.isCellLocked(a)&&b[mxConstants.STYLE_RESIZABLE]!=0};mxGraph.prototype.isCellsResizable=function(){return this.cellsResizable};
-mxGraph.prototype.setCellsResizable=function(a){this.cellsResizable=a};mxGraph.prototype.isTerminalPointMovable=function(){return true};mxGraph.prototype.isCellBendable=function(a){var b=this.view.getState(a),b=b!=null?b.style:this.getCellStyle(a);return this.isCellsBendable()&&!this.isCellLocked(a)&&b[mxConstants.STYLE_BENDABLE]!=0};mxGraph.prototype.isCellsBendable=function(){return this.cellsBendable};mxGraph.prototype.setCellsBendable=function(a){this.cellsBendable=a};
-mxGraph.prototype.isCellEditable=function(a){var b=this.view.getState(a),b=b!=null?b.style:this.getCellStyle(a);return this.isCellsEditable()&&!this.isCellLocked(a)&&b[mxConstants.STYLE_EDITABLE]!=0};mxGraph.prototype.isCellsEditable=function(){return this.cellsEditable};mxGraph.prototype.setCellsEditable=function(a){this.cellsEditable=a};mxGraph.prototype.isCellDisconnectable=function(a){return this.isCellsDisconnectable()&&!this.isCellLocked(a)};mxGraph.prototype.isCellsDisconnectable=function(){return this.cellsDisconnectable};
-mxGraph.prototype.setCellsDisconnectable=function(a){this.cellsDisconnectable=a};mxGraph.prototype.isValidSource=function(a){return a==null&&this.allowDanglingEdges||a!=null&&(!this.model.isEdge(a)||this.connectableEdges)&&this.isCellConnectable(a)};mxGraph.prototype.isValidTarget=function(a){return this.isValidSource(a)};mxGraph.prototype.isValidConnection=function(a,b){return this.isValidSource(a)&&this.isValidTarget(b)};mxGraph.prototype.setConnectable=function(a){this.connectionHandler.setEnabled(a)};
-mxGraph.prototype.isConnectable=function(){return this.connectionHandler.isEnabled()};mxGraph.prototype.setTooltips=function(a){this.tooltipHandler.setEnabled(a)};mxGraph.prototype.setPanning=function(a){this.panningHandler.panningEnabled=a};mxGraph.prototype.isEditing=function(a){if(this.cellEditor!=null){var b=this.cellEditor.getEditingCell();return a==null?b!=null:a==b}return false};
-mxGraph.prototype.isAutoSizeCell=function(a){var b=this.view.getState(a),a=b!=null?b.style:this.getCellStyle(a);return this.isAutoSizeCells()||a[mxConstants.STYLE_AUTOSIZE]==1};mxGraph.prototype.isAutoSizeCells=function(){return this.autoSizeCells};mxGraph.prototype.setAutoSizeCells=function(a){this.autoSizeCells=a};mxGraph.prototype.isExtendParent=function(a){return!this.getModel().isEdge(a)&&this.isExtendParents()};mxGraph.prototype.isExtendParents=function(){return this.extendParents};
-mxGraph.prototype.setExtendParents=function(a){this.extendParents=a};mxGraph.prototype.isExtendParentsOnAdd=function(){return this.extendParentsOnAdd};mxGraph.prototype.setExtendParentsOnAdd=function(a){this.extendParentsOnAdd=a};mxGraph.prototype.isConstrainChild=function(a){return this.isConstrainChildren()&&!this.getModel().isEdge(this.getModel().getParent(a))};mxGraph.prototype.isConstrainChildren=function(){return this.constrainChildren};
-mxGraph.prototype.setConstrainChildren=function(a){this.constrainChildren=a};mxGraph.prototype.isAllowNegativeCoordinates=function(){return this.allowNegativeCoordinates};mxGraph.prototype.setAllowNegativeCoordinates=function(a){this.allowNegativeCoordinates=a};mxGraph.prototype.getOverlap=function(a){return this.isAllowOverlapParent(a)?this.defaultOverlap:0};mxGraph.prototype.isAllowOverlapParent=function(){return false};
-mxGraph.prototype.getFoldableCells=function(a,b){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.isCellFoldable(a,b)}))};mxGraph.prototype.isCellFoldable=function(a){var b=this.view.getState(a),b=b!=null?b.style:this.getCellStyle(a);return this.model.getChildCount(a)>0&&b[mxConstants.STYLE_FOLDABLE]!=0};
-mxGraph.prototype.isValidDropTarget=function(a,b,c){return a!=null&&(this.isSplitEnabled()&&this.isSplitTarget(a,b,c)||!this.model.isEdge(a)&&(this.isSwimlane(a)||this.model.getChildCount(a)>0&&!this.isCellCollapsed(a)))};
-mxGraph.prototype.isSplitTarget=function(a,b){if(this.model.isEdge(a)&&b!=null&&b.length==1&&this.isCellConnectable(b[0])&&this.getEdgeValidationError(a,this.model.getTerminal(a,true),b[0])==null){var c=this.model.getTerminal(a,true),d=this.model.getTerminal(a,false);return!this.model.isAncestor(b[0],c)&&!this.model.isAncestor(b[0],d)}return false};
-mxGraph.prototype.getDropTarget=function(a,b,c){if(!this.isSwimlaneNesting())for(var d=0;d<a.length;d++)if(this.isSwimlane(a[d]))return null;d=mxUtils.convertPoint(this.container,mxEvent.getClientX(b),mxEvent.getClientY(b));d.x=d.x-this.panDx;d.y=d.y-this.panDy;d=this.getSwimlaneAt(d.x,d.y);if(c==null)c=d;else if(d!=null){for(var e=this.model.getParent(d);e!=null&&this.isSwimlane(e)&&e!=c;)e=this.model.getParent(e);e==c&&(c=d)}for(;c!=null&&!this.isValidDropTarget(c,a,b)&&!this.model.isLayer(c);)c=
-this.model.getParent(c);return!this.model.isLayer(c)&&mxUtils.indexOf(a,c)<0?c:null};mxGraph.prototype.getDefaultParent=function(){var a=this.defaultParent;if(a==null){a=this.getCurrentRoot();a==null&&(a=this.model.getChildAt(this.model.getRoot(),0))}return a};mxGraph.prototype.setDefaultParent=function(a){this.defaultParent=a};mxGraph.prototype.getSwimlane=function(a){for(;a!=null&&!this.isSwimlane(a);)a=this.model.getParent(a);return a};
-mxGraph.prototype.getSwimlaneAt=function(a,b,c){c=c||this.getDefaultParent();if(c!=null)for(var d=this.model.getChildCount(c),e=0;e<d;e++){var f=this.model.getChildAt(c,e),g=this.getSwimlaneAt(a,b,f);if(g!=null)return g;if(this.isSwimlane(f)&&this.intersects(this.view.getState(f),a,b))return f}return null};
-mxGraph.prototype.getCellAt=function(a,b,c,d,e){d=d!=null?d:true;e=e!=null?e:true;c=c!=null?c:this.getDefaultParent();if(c!=null)for(var f=this.model.getChildCount(c)-1;f>=0;f--){var g=this.model.getChildAt(c,f),h=this.getCellAt(a,b,g,d,e);if(h!=null)return h;if(this.isCellVisible(g)&&(e&&this.model.isEdge(g)||d&&this.model.isVertex(g))&&this.intersects(this.view.getState(g),a,b))return g}return null};
-mxGraph.prototype.intersects=function(a,b,c){if(a!=null){var d=a.absolutePoints;if(d!=null)for(var a=this.tolerance*this.tolerance,e=d[0],f=1;f<d.length;f++){var g=d[f];if(mxUtils.ptSegDistSq(e.x,e.y,g.x,g.y,b,c)<=a)return true;e=g}else if(mxUtils.contains(a,b,c))return true}return false};
-mxGraph.prototype.hitsSwimlaneContent=function(a,b,c){var d=this.getView().getState(a),a=this.getStartSize(a);if(d!=null){var e=this.getView().getScale(),b=b-d.x,c=c-d.y;if(a.width>0&&b>0&&b>a.width*e||a.height>0&&c>0&&c>a.height*e)return true}return false};mxGraph.prototype.getChildVertices=function(a){return this.getChildCells(a,true,false)};mxGraph.prototype.getChildEdges=function(a){return this.getChildCells(a,false,true)};
-mxGraph.prototype.getChildCells=function(a,b,c){a=a!=null?a:this.getDefaultParent();a=this.model.getChildCells(a,b!=null?b:false,c!=null?c:false);b=[];for(c=0;c<a.length;c++)this.isCellVisible(a[c])&&b.push(a[c]);return b};mxGraph.prototype.getConnections=function(a,b){return this.getEdges(a,b,true,true,false)};mxGraph.prototype.getIncomingEdges=function(a,b){return this.getEdges(a,b,true,false,false)};mxGraph.prototype.getOutgoingEdges=function(a,b){return this.getEdges(a,b,false,true,false)};
-mxGraph.prototype.getEdges=function(a,b,c,d,e,f){for(var c=c!=null?c:true,d=d!=null?d:true,e=e!=null?e:true,f=f!=null?f:false,g=[],h=this.isCellCollapsed(a),k=this.model.getChildCount(a),i=0;i<k;i++){var l=this.model.getChildAt(a,i);if(h||!this.isCellVisible(l))g=g.concat(this.model.getEdges(l,c,d))}g=g.concat(this.model.getEdges(a,c,d));h=[];for(i=0;i<g.length;i++){l=this.view.getState(g[i]);k=l!=null?l.getVisibleTerminal(true):this.view.getVisibleTerminal(g[i],true);l=l!=null?l.getVisibleTerminal(false):
-this.view.getVisibleTerminal(g[i],false);(e&&k==l||k!=l&&(c&&l==a&&(b==null||this.isValidAncestor(k,b,f))||d&&k==a&&(b==null||this.isValidAncestor(l,b,f))))&&h.push(g[i])}return h};mxGraph.prototype.isValidAncestor=function(a,b,c){return c?this.model.isAncestor(b,a):this.model.getParent(a)==b};
-mxGraph.prototype.getOpposites=function(a,b,c,d){var c=c!=null?c:true,d=d!=null?d:true,e=[],f={};if(a!=null)for(var g=0;g<a.length;g++){var h=this.view.getState(a[g]),k=h!=null?h.getVisibleTerminal(true):this.view.getVisibleTerminal(a[g],true),h=h!=null?h.getVisibleTerminal(false):this.view.getVisibleTerminal(a[g],false);if(k==b&&h!=null&&h!=b&&d){var i=mxCellPath.create(h);if(f[i]==null){f[i]=h;e.push(h)}}else if(h==b&&k!=null&&k!=b&&c){i=mxCellPath.create(k);if(f[i]==null){f[i]=k;e.push(k)}}}return e};
-mxGraph.prototype.getEdgesBetween=function(a,b,c){for(var c=c!=null?c:false,d=this.getEdges(a),e=[],f=0;f<d.length;f++){var g=this.view.getState(d[f]),h=g!=null?g.getVisibleTerminal(true):this.view.getVisibleTerminal(d[f],true),g=g!=null?g.getVisibleTerminal(false):this.view.getVisibleTerminal(d[f],false);(h==a&&g==b||!c&&h==b&&g==a)&&e.push(d[f])}return e};
-mxGraph.prototype.getPointForEvent=function(a,b){var c=mxUtils.convertPoint(this.container,mxEvent.getClientX(a),mxEvent.getClientY(a)),d=this.view.scale,e=this.view.translate,f=b!=false?this.gridSize/2:0;c.x=this.snap(c.x/d-e.x-f);c.y=this.snap(c.y/d-e.y-f);return c};
-mxGraph.prototype.getCells=function(a,b,c,d,e,f){f=f!=null?f:[];if(c>0||d>0){var g=a+c,h=b+d,e=e||this.getDefaultParent();if(e!=null)for(var k=this.model.getChildCount(e),i=0;i<k;i++){var l=this.model.getChildAt(e,i),m=this.view.getState(l);this.isCellVisible(l)&&m!=null&&(m.x>=a&&m.y>=b&&m.x+m.width<=g&&m.y+m.height<=h?f.push(l):this.getCells(a,b,c,d,l,f))}}return f};
-mxGraph.prototype.getCellsBeyond=function(a,b,c,d,e){var f=[];if(d||e){c==null&&(c=this.getDefaultParent());if(c!=null)for(var g=this.model.getChildCount(c),h=0;h<g;h++){var k=this.model.getChildAt(c,h),i=this.view.getState(k);this.isCellVisible(k)&&i!=null&&(!d||i.x>=a)&&(!e||i.y>=b)&&f.push(k)}}return f};
-mxGraph.prototype.findTreeRoots=function(a,b,c){var b=b!=null?b:false,c=c!=null?c:false,d=[];if(a!=null){for(var e=this.getModel(),f=e.getChildCount(a),g=null,h=0,k=0;k<f;k++){var i=e.getChildAt(a,k);if(this.model.isVertex(i)&&this.isCellVisible(i)){for(var l=this.getConnections(i,b?a:null),m=0,n=0,o=0;o<l.length;o++)this.view.getVisibleTerminal(l[o],true)==i?m++:n++;(c&&m==0&&n>0||!c&&n==0&&m>0)&&d.push(i);l=c?n-m:m-n;if(l>h){h=l;g=i}}}d.length==0&&g!=null&&d.push(g)}return d};
-mxGraph.prototype.traverse=function(a,b,c,d,e){if(c!=null&&a!=null){var b=b!=null?b:true,e=e||[],f=mxCellPath.create(a);if(e[f]==null){e[f]=a;d=c(a,d);if(d==null||d){d=this.model.getEdgeCount(a);if(d>0)for(f=0;f<d;f++){var g=this.model.getEdgeAt(a,f),h=this.model.getTerminal(g,true)==a;(!b||h)&&this.traverse(this.model.getTerminal(g,!h),b,c,g,e)}}}}};mxGraph.prototype.isCellSelected=function(a){return this.getSelectionModel().isSelected(a)};mxGraph.prototype.isSelectionEmpty=function(){return this.getSelectionModel().isEmpty()};
-mxGraph.prototype.clearSelection=function(){return this.getSelectionModel().clear()};mxGraph.prototype.getSelectionCount=function(){return this.getSelectionModel().cells.length};mxGraph.prototype.getSelectionCell=function(){return this.getSelectionModel().cells[0]};mxGraph.prototype.getSelectionCells=function(){return this.getSelectionModel().cells.slice()};mxGraph.prototype.setSelectionCell=function(a){this.getSelectionModel().setCell(a)};mxGraph.prototype.setSelectionCells=function(a){this.getSelectionModel().setCells(a)};
-mxGraph.prototype.addSelectionCell=function(a){this.getSelectionModel().addCell(a)};mxGraph.prototype.addSelectionCells=function(a){this.getSelectionModel().addCells(a)};mxGraph.prototype.removeSelectionCell=function(a){this.getSelectionModel().removeCell(a)};mxGraph.prototype.removeSelectionCells=function(a){this.getSelectionModel().removeCells(a)};mxGraph.prototype.selectRegion=function(a,b){var c=this.getCells(a.x,a.y,a.width,a.height);this.selectCellsForEvent(c,b);return c};
-mxGraph.prototype.selectNextCell=function(){this.selectCell(true)};mxGraph.prototype.selectPreviousCell=function(){this.selectCell()};mxGraph.prototype.selectParentCell=function(){this.selectCell(false,true)};mxGraph.prototype.selectChildCell=function(){this.selectCell(false,false,true)};
-mxGraph.prototype.selectCell=function(a,b,c){var d=this.selectionModel,e=d.cells.length>0?d.cells[0]:null;d.cells.length>1&&d.clear();var d=e!=null?this.model.getParent(e):this.getDefaultParent(),f=this.model.getChildCount(d);if(e==null&&f>0){a=this.model.getChildAt(d,0);this.setSelectionCell(a)}else if((e==null||b)&&this.view.getState(d)!=null&&this.model.getGeometry(d)!=null)this.getCurrentRoot()!=d&&this.setSelectionCell(d);else if(e!=null&&c){if(this.model.getChildCount(e)>0){a=this.model.getChildAt(e,
-0);this.setSelectionCell(a)}}else if(f>0){b=d.getIndex(e);if(a){b++;a=this.model.getChildAt(d,b%f)}else{b--;a=this.model.getChildAt(d,b<0?f-1:b)}this.setSelectionCell(a)}};mxGraph.prototype.selectAll=function(a){a=a||this.getDefaultParent();a=this.model.getChildren(a);a!=null&&this.setSelectionCells(a)};mxGraph.prototype.selectVertices=function(a){this.selectCells(true,false,a)};mxGraph.prototype.selectEdges=function(a){this.selectCells(false,true,a)};
-mxGraph.prototype.selectCells=function(a,b,c){c=c||this.getDefaultParent();this.setSelectionCells(this.model.filterDescendants(mxUtils.bind(this,function(c){return this.view.getState(c)!=null&&this.model.getChildCount(c)==0&&(this.model.isVertex(c)&&a||this.model.isEdge(c)&&b)}),c))};mxGraph.prototype.selectCellForEvent=function(a,b){var c=this.isCellSelected(a);this.isToggleEvent(b)?c?this.removeSelectionCell(a):this.addSelectionCell(a):(!c||this.getSelectionCount()!=1)&&this.setSelectionCell(a)};
-mxGraph.prototype.selectCellsForEvent=function(a,b){this.isToggleEvent(b)?this.addSelectionCells(a):this.setSelectionCells(a)};
-mxGraph.prototype.createHandler=function(a){var b=null;if(a!=null)if(this.model.isEdge(a.cell)){b=this.view.getEdgeStyle(a);b=this.isLoop(a)||b==mxEdgeStyle.ElbowConnector||b==mxEdgeStyle.SideToSide||b==mxEdgeStyle.TopToBottom?new mxElbowEdgeHandler(a):b==mxEdgeStyle.SegmentConnector||b==mxEdgeStyle.OrthConnector?new mxEdgeSegmentHandler(a):new mxEdgeHandler(a)}else b=new mxVertexHandler(a);return b};
-mxGraph.prototype.addMouseListener=function(a){if(this.mouseListeners==null)this.mouseListeners=[];this.mouseListeners.push(a)};mxGraph.prototype.removeMouseListener=function(a){if(this.mouseListeners!=null)for(var b=0;b<this.mouseListeners.length;b++)if(this.mouseListeners[b]==a){this.mouseListeners.splice(b,1);break}};
-mxGraph.prototype.updateMouseEvent=function(a){if(a.graphX==null||a.graphY==null){var b=mxUtils.convertPoint(this.container,a.getX(),a.getY());a.graphX=b.x-this.panDx;a.graphY=b.y-this.panDy}};
-mxGraph.prototype.fireMouseEvent=function(a,b,c){c==null&&(c=this);this.updateMouseEvent(b);if(a==mxEvent.MOUSE_DOWN)this.isMouseDown=true;if(mxClient.IS_TOUCH&&this.doubleTapEnabled&&a==mxEvent.MOUSE_DOWN){var d=(new Date).getTime();if(d-this.lastTouchTime<this.doubleTapTimeout&&Math.abs(this.lastTouchX-b.getX())<this.doubleTapTolerance&&Math.abs(this.lastTouchY-b.getY())<this.doubleTapTolerance){this.lastTouchTime=0;this.dblClick(b.getEvent(),b.getCell());b.getEvent().cancelBubble=true}else{this.lastTouchX=
-b.getX();this.lastTouchY=b.getY();this.lastTouchTime=d}}d=b.getEvent().detail!=2;if(mxClient.IS_IE&&document.compatMode=="CSS1Compat"){if(this.lastMouseX!=null&&Math.abs(this.lastMouseX-b.getX())>this.doubleTapTolerance||this.lastMouseY!=null&&Math.abs(this.lastMouseY-b.getY())>this.doubleTapTolerance)d=true;if(a==mxEvent.MOUSE_UP){this.lastMouseX=b.getX();this.lastMouseY=b.getY()}}if((a!=mxEvent.MOUSE_UP||this.isMouseDown)&&d){if(a==mxEvent.MOUSE_UP)this.isMouseDown=false;if(!this.isEditing()&&(mxClient.IS_OP||
-mxClient.IS_SF||mxClient.IS_GC||mxClient.IS_IE&&mxClient.IS_SVG||b.getEvent().target!=this.container)){a==mxEvent.MOUSE_MOVE&&(this.isMouseDown&&this.autoScroll)&&this.scrollPointToVisible(b.getGraphX(),b.getGraphY(),this.autoExtend);if(this.mouseListeners!=null){c=[c,b];b.getEvent().returnValue=true;for(d=0;d<this.mouseListeners.length;d++){var e=this.mouseListeners[d];a==mxEvent.MOUSE_DOWN?e.mouseDown.apply(e,c):a==mxEvent.MOUSE_MOVE?e.mouseMove.apply(e,c):a==mxEvent.MOUSE_UP&&e.mouseUp.apply(e,
-c)}}a==mxEvent.MOUSE_UP&&this.click(b)}}else if(a==mxEvent.MOUSE_UP)this.isMouseDown=false};
-mxGraph.prototype.destroy=function(){if(!this.destroyed){this.destroyed=true;this.tooltipHandler!=null&&this.tooltipHandler.destroy();this.selectionCellsHandler!=null&&this.selectionCellsHandler.destroy();this.panningHandler!=null&&this.panningHandler.destroy();this.connectionHandler!=null&&this.connectionHandler.destroy();this.graphHandler!=null&&this.graphHandler.destroy();this.cellEditor!=null&&this.cellEditor.destroy();this.view!=null&&this.view.destroy();if(this.model!=null&&this.graphModelChangeListener!=
-null){this.model.removeListener(this.graphModelChangeListener);this.graphModelChangeListener=null}this.container=null}};function mxCellOverlay(a,b,c,d,e,f){this.image=a;this.tooltip=b;this.align=c!=null?c:this.align;this.verticalAlign=d!=null?d:this.verticalAlign;this.offset=e!=null?e:new mxPoint;this.cursor=f!=null?f:"help"}mxCellOverlay.prototype=new mxEventSource;mxCellOverlay.prototype.constructor=mxCellOverlay;mxCellOverlay.prototype.image=null;mxCellOverlay.prototype.tooltip=null;
-mxCellOverlay.prototype.align=mxConstants.ALIGN_RIGHT;mxCellOverlay.prototype.verticalAlign=mxConstants.ALIGN_BOTTOM;mxCellOverlay.prototype.offset=null;mxCellOverlay.prototype.cursor=null;mxCellOverlay.prototype.defaultOverlap=0.5;
-mxCellOverlay.prototype.getBounds=function(a){var b=a.view.graph.getModel().isEdge(a.cell),c=a.view.scale,d=null,e=this.image.width,f=this.image.height;if(b){b=a.absolutePoints;if(b.length%2==1)d=b[Math.floor(b.length/2)];else{d=b.length/2;a=b[d-1];b=b[d];d=new mxPoint(a.x+(b.x-a.x)/2,a.y+(b.y-a.y)/2)}}else{d=new mxPoint;d.x=this.align==mxConstants.ALIGN_LEFT?a.x:this.align==mxConstants.ALIGN_CENTER?a.x+a.width/2:a.x+a.width;d.y=this.verticalAlign==mxConstants.ALIGN_TOP?a.y:this.verticalAlign==mxConstants.ALIGN_MIDDLE?
-a.y+a.height/2:a.y+a.height}return new mxRectangle(d.x-(e*this.defaultOverlap-this.offset.x)*c,d.y-(f*this.defaultOverlap-this.offset.y)*c,e*c,f*c)};mxCellOverlay.prototype.toString=function(){return this.tooltip};function mxOutline(a,b){this.source=a;b!=null&&this.init(b)}mxOutline.prototype.source=null;mxOutline.prototype.outline=null;mxOutline.prototype.graphRenderHint=mxConstants.RENDERING_HINT_FASTER;mxOutline.prototype.enabled=!0;mxOutline.prototype.showViewport=!0;
-mxOutline.prototype.border=10;mxOutline.prototype.sizerSize=8;mxOutline.prototype.updateOnPan=!1;mxOutline.prototype.sizerImage=null;mxOutline.prototype.suspended=!1;
-mxOutline.prototype.init=function(a){this.outline=new mxGraph(a,this.source.getModel(),this.graphRenderHint,this.source.getStylesheet());this.outline.foldingEnabled=false;this.outline.autoScroll=false;var b=this.outline.graphModelChanged;this.outline.graphModelChanged=mxUtils.bind(this,function(a){!this.suspended&&this.outline!=null&&b.apply(this.outline,arguments)});if(mxClient.IS_SVG){a=this.outline.getView().getCanvas().parentNode;a.setAttribute("shape-rendering","optimizeSpeed");a.setAttribute("image-rendering",
-"optimizeSpeed")}this.outline.labelsVisible=false;this.outline.setEnabled(false);this.updateHandler=mxUtils.bind(this,function(){!this.suspended&&!this.active&&this.update()});this.source.getModel().addListener(mxEvent.CHANGE,this.updateHandler);this.outline.addMouseListener(this);a=this.source.getView();a.addListener(mxEvent.SCALE,this.updateHandler);a.addListener(mxEvent.TRANSLATE,this.updateHandler);a.addListener(mxEvent.SCALE_AND_TRANSLATE,this.updateHandler);a.addListener(mxEvent.DOWN,this.updateHandler);
-a.addListener(mxEvent.UP,this.updateHandler);mxEvent.addListener(this.source.container,"scroll",this.updateHandler);this.panHandler=mxUtils.bind(this,function(a){this.updateOnPan&&this.updateHandler.apply(this,arguments)});this.source.addListener(mxEvent.PAN,this.panHandler);this.refreshHandler=mxUtils.bind(this,function(){this.outline.setStylesheet(this.source.getStylesheet());this.outline.refresh()});this.source.addListener(mxEvent.REFRESH,this.refreshHandler);this.bounds=new mxRectangle(0,0,0,
-0);this.selectionBorder=new mxRectangleShape(this.bounds,null,mxConstants.OUTLINE_COLOR,mxConstants.OUTLINE_STROKEWIDTH);this.selectionBorder.dialect=this.outline.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;this.selectionBorder.crisp=true;this.selectionBorder.init(this.outline.getView().getOverlayPane());mxEvent.redirectMouseEvents(this.selectionBorder.node,this.outline);this.selectionBorder.node.style.background="";this.sizer=this.createSizer();this.sizer.init(this.outline.getView().getOverlayPane());
-if(this.enabled)this.sizer.node.style.cursor="pointer";mxEvent.addListener(this.sizer.node,mxClient.IS_TOUCH?"touchstart":"mousedown",mxUtils.bind(this,function(a){this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(a))}));this.selectionBorder.node.style.display=this.showViewport?"":"none";this.sizer.node.style.display=this.selectionBorder.node.style.display;this.selectionBorder.node.style.cursor="move";this.update(false)};mxOutline.prototype.isEnabled=function(){return this.enabled};
-mxOutline.prototype.setEnabled=function(a){this.enabled=a};mxOutline.prototype.setZoomEnabled=function(a){this.sizer.node.style.visibility=a?"visible":"hidden"};mxOutline.prototype.refresh=function(){this.update(true)};
-mxOutline.prototype.createSizer=function(){if(this.sizerImage!=null){var a=new mxImageShape(new mxRectangle(0,0,this.sizerImage.width,this.sizerImage.height),this.sizerImage.src);a.dialect=this.outline.dialect}else{a=new mxRectangleShape(new mxRectangle(0,0,this.sizerSize,this.sizerSize),mxConstants.OUTLINE_HANDLE_FILLCOLOR,mxConstants.OUTLINE_HANDLE_STROKECOLOR);a.dialect=this.outline.dialect;a.crisp=true}return a};
-mxOutline.prototype.getSourceContainerSize=function(){return new mxRectangle(0,0,this.source.container.scrollWidth,this.source.container.scrollHeight)};mxOutline.prototype.getOutlineOffset=function(){return null};
-mxOutline.prototype.update=function(a){if(this.source!=null){var b=this.source.view.scale,c=this.source.getGraphBounds(),c=new mxRectangle(c.x/b+this.source.panDx,c.y/b+this.source.panDy,c.width/b,c.height/b),d=new mxRectangle(0,0,this.source.container.clientWidth/b,this.source.container.clientHeight/b),e=c.clone();e.add(d);var f=this.getSourceContainerSize(),d=Math.max(f.width/b,e.width),b=Math.max(f.height/b,e.height),e=Math.max(0,this.outline.container.clientWidth-this.border),f=Math.max(0,this.outline.container.clientHeight-
-this.border),e=Math.min(e/d,f/b);if(e>0){if(this.outline.getView().scale!=e){this.outline.getView().scale=e;a=true}d=this.outline.getView();d.currentRoot!=this.source.getView().currentRoot&&d.setCurrentRoot(this.source.getView().currentRoot);var b=this.source.view.translate,f=b.x+this.source.panDx,g=b.y+this.source.panDy,e=this.getOutlineOffset(e);if(e!=null){f=f+e.x;g=g+e.y}c.x<0&&(f=f-c.x);c.y<0&&(g=g-c.y);if(d.translate.x!=f||d.translate.y!=g){d.translate.x=f;d.translate.y=g;a=true}var c=d.translate,
-e=this.source.getView().scale,f=e/d.scale,g=1/d.scale,h=this.source.container;this.bounds=new mxRectangle((c.x-b.x-this.source.panDx)/g,(c.y-b.y-this.source.panDy)/g,h.clientWidth/f,h.clientHeight/f);this.bounds.x=this.bounds.x+this.source.container.scrollLeft*d.scale/e;this.bounds.y=this.bounds.y+this.source.container.scrollTop*d.scale/e;c=this.selectionBorder.bounds;if(c.x!=this.bounds.x||c.y!=this.bounds.y||c.width!=this.bounds.width||c.height!=this.bounds.height){this.selectionBorder.bounds=this.bounds;
-this.selectionBorder.redraw()}c=this.sizer.bounds;d=new mxRectangle(this.bounds.x+this.bounds.width-c.width/2,this.bounds.y+this.bounds.height-c.height/2,c.width,c.height);if(c.x!=d.x||c.y!=d.y||c.width!=d.width||c.height!=d.height){this.sizer.bounds=d;this.sizer.node.style.visibility!="hidden"&&this.sizer.redraw()}a&&this.outline.view.revalidate()}}};
-mxOutline.prototype.mouseDown=function(a,b){if(this.enabled&&this.showViewport){this.zoom=b.isSource(this.sizer);this.startX=b.getX();this.startY=b.getY();this.active=true;if(this.source.useScrollbarsForPanning&&mxUtils.hasScrollbars(this.source.container)){this.dx0=this.source.container.scrollLeft;this.dy0=this.source.container.scrollTop}else this.dy0=this.dx0=0}b.consume()};
-mxOutline.prototype.mouseMove=function(a,b){if(this.active){this.selectionBorder.node.style.display=this.showViewport?"":"none";this.sizer.node.style.display=this.selectionBorder.node.style.display;var c=b.getX()-this.startX,d=b.getY()-this.startY,e=null;if(this.zoom){e=this.source.container;d=c/(e.clientWidth/e.clientHeight);e=new mxRectangle(this.bounds.x,this.bounds.y,Math.max(1,this.bounds.width+c),Math.max(1,this.bounds.height+d));this.selectionBorder.bounds=e;this.selectionBorder.redraw()}else{var f=
-this.outline.getView().scale,e=new mxRectangle(this.bounds.x+c,this.bounds.y+d,this.bounds.width,this.bounds.height);this.selectionBorder.bounds=e;this.selectionBorder.redraw();c=c/f*this.source.getView().scale;d=d/f*this.source.getView().scale;this.source.panGraph(-c-this.dx0,-d-this.dy0)}c=this.sizer.bounds;this.sizer.bounds=new mxRectangle(e.x+e.width-c.width/2,e.y+e.height-c.height/2,c.width,c.height);this.sizer.node.style.visibility!="hidden"&&this.sizer.redraw();b.consume()}};
-mxOutline.prototype.mouseUp=function(a,b){if(this.active){var c=b.getX()-this.startX,d=b.getY()-this.startY;if(Math.abs(c)>0||Math.abs(d)>0){if(this.zoom){var d=this.selectionBorder.bounds.width,e=this.source.getView().scale;this.source.zoomTo(e-c*e/d,false)}else if(!this.source.useScrollbarsForPanning||!mxUtils.hasScrollbars(this.source.container)){this.source.panGraph(0,0);c=c/this.outline.getView().scale;d=d/this.outline.getView().scale;e=this.source.getView().translate;this.source.getView().setTranslate(e.x-
-c,e.y-d)}this.update();b.consume()}this.index=null;this.active=false}};
-mxOutline.prototype.destroy=function(){if(this.source!=null){this.source.removeListener(this.panHandler);this.source.removeListener(this.refreshHandler);this.source.getModel().removeListener(this.updateHandler);this.source.getView().removeListener(this.updateHandler);mxEvent.addListener(this.source.container,"scroll",this.updateHandler);this.source=null}if(this.outline!=null){this.outline.removeMouseListener(this);this.outline.destroy();this.outline=null}if(this.selectionBorder!=null){this.selectionBorder.destroy();
-this.selectionBorder=null}if(this.sizer!=null){this.sizer.destroy();this.sizer=null}};function mxMultiplicity(a,b,c,d,e,f,g,h,k,i){this.source=a;this.type=b;this.attr=c;this.value=d;this.min=e!=null?e:0;this.max=f!=null?f:"n";this.validNeighbors=g;this.countError=mxResources.get(h)||h;this.typeError=mxResources.get(k)||k;this.validNeighborsAllowed=i!=null?i:true}mxMultiplicity.prototype.type=null;mxMultiplicity.prototype.attr=null;mxMultiplicity.prototype.value=null;
-mxMultiplicity.prototype.source=null;mxMultiplicity.prototype.min=null;mxMultiplicity.prototype.max=null;mxMultiplicity.prototype.validNeighbors=null;mxMultiplicity.prototype.validNeighborsAllowed=!0;mxMultiplicity.prototype.countError=null;mxMultiplicity.prototype.typeError=null;
-mxMultiplicity.prototype.check=function(a,b,c,d,e,f){var g="";if(this.source&&this.checkTerminal(a,c,b)||!this.source&&this.checkTerminal(a,d,b)){if(this.countError!=null&&(this.source&&(this.max==0||e>=this.max)||!this.source&&(this.max==0||f>=this.max)))g=g+(this.countError+"\n");this.validNeighbors!=null&&(this.typeError!=null&&this.validNeighbors.length>0)&&(this.checkNeighbors(a,b,c,d)||(g=g+(this.typeError+"\n")))}return g.length>0?g:null};
-mxMultiplicity.prototype.checkNeighbors=function(a,b,c,d){for(var b=a.model.getValue(c),d=a.model.getValue(d),c=!this.validNeighborsAllowed,e=this.validNeighbors,f=0;f<e.length;f++)if(this.source&&this.checkType(a,d,e[f])){c=this.validNeighborsAllowed;break}else if(!this.source&&this.checkType(a,b,e[f])){c=this.validNeighborsAllowed;break}return c};mxMultiplicity.prototype.checkTerminal=function(a,b){var c=a.model.getValue(b);return this.checkType(a,c,this.type,this.attr,this.value)};
-mxMultiplicity.prototype.checkType=function(a,b,c,d,e){return b!=null?isNaN(b.nodeType)?b==c:mxUtils.isNode(b,c,d,e):false};function mxLayoutManager(a){this.undoHandler=mxUtils.bind(this,function(a,c){this.isEnabled()&&this.beforeUndo(c.getProperty("edit"))});this.moveHandler=mxUtils.bind(this,function(a,c){this.isEnabled()&&this.cellsMoved(c.getProperty("cells"),c.getProperty("event"))});this.setGraph(a)}mxLayoutManager.prototype=new mxEventSource;mxLayoutManager.prototype.constructor=mxLayoutManager;
-mxLayoutManager.prototype.graph=null;mxLayoutManager.prototype.bubbling=!0;mxLayoutManager.prototype.enabled=!0;mxLayoutManager.prototype.updateHandler=null;mxLayoutManager.prototype.moveHandler=null;mxLayoutManager.prototype.isEnabled=function(){return this.enabled};mxLayoutManager.prototype.setEnabled=function(a){this.enabled=a};mxLayoutManager.prototype.isBubbling=function(){return this.bubbling};mxLayoutManager.prototype.setBubbling=function(a){this.bubbling=a};
-mxLayoutManager.prototype.getGraph=function(){return this.graph};mxLayoutManager.prototype.setGraph=function(a){if(this.graph!=null){var b=this.graph.getModel();b.removeListener(this.undoHandler);this.graph.removeListener(this.moveHandler)}this.graph=a;if(this.graph!=null){b=this.graph.getModel();b.addListener(mxEvent.BEFORE_UNDO,this.undoHandler);this.graph.addListener(mxEvent.MOVE_CELLS,this.moveHandler)}};mxLayoutManager.prototype.getLayout=function(){return null};
-mxLayoutManager.prototype.beforeUndo=function(a){var a=this.getCellsForChanges(a.changes),b=this.getGraph().getModel();if(this.isBubbling())for(var c=b.getParents(a);c.length>0;){a=a.concat(c);c=b.getParents(c)}this.layoutCells(mxUtils.sortCells(a,false))};
-mxLayoutManager.prototype.cellsMoved=function(a,b){if(a!=null&&b!=null)for(var c=mxUtils.convertPoint(this.getGraph().container,mxEvent.getClientX(b),mxEvent.getClientY(b)),d=this.getGraph().getModel(),e=0;e<a.length;e++){var f=this.getLayout(d.getParent(a[e]));f!=null&&f.moveCell(a[e],c.x,c.y)}};
-mxLayoutManager.prototype.getCellsForChanges=function(a){for(var b=[],c={},d=0;d<a.length;d++){var e=a[d];if(e instanceof mxRootChange)return[];for(var e=this.getCellsForChange(e),f=0;f<e.length;f++)if(e[f]!=null){var g=mxCellPath.create(e[f]);if(c[g]==null){c[g]=e[f];b.push(e[f])}}}return b};
-mxLayoutManager.prototype.getCellsForChange=function(a){var b=this.getGraph().getModel();return a instanceof mxChildChange?[a.child,a.previous,b.getParent(a.child)]:a instanceof mxTerminalChange||a instanceof mxGeometryChange?[a.cell,b.getParent(a.cell)]:[]};
-mxLayoutManager.prototype.layoutCells=function(a){if(a.length>0){var b=this.getGraph().getModel();b.beginUpdate();try{for(var c=null,d=0;d<a.length;d++)if(a[d]!=b.getRoot()&&a[d]!=c){c=a[d];this.executeLayout(this.getLayout(c),c)}this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS,"cells",a))}finally{b.endUpdate()}}};mxLayoutManager.prototype.executeLayout=function(a,b){a!=null&&b!=null&&a.execute(b)};mxLayoutManager.prototype.destroy=function(){this.setGraph(null)};
-function mxSpaceManager(a,b,c,d){this.resizeHandler=mxUtils.bind(this,function(a,b){this.isEnabled()&&this.cellsResized(b.getProperty("cells"))});this.foldHandler=mxUtils.bind(this,function(a,b){this.isEnabled()&&this.cellsResized(b.getProperty("cells"))});this.shiftRightwards=b!=null?b:true;this.shiftDownwards=c!=null?c:true;this.extendParents=d!=null?d:true;this.setGraph(a)}mxSpaceManager.prototype=new mxEventSource;mxSpaceManager.prototype.constructor=mxSpaceManager;
-mxSpaceManager.prototype.graph=null;mxSpaceManager.prototype.enabled=!0;mxSpaceManager.prototype.shiftRightwards=!0;mxSpaceManager.prototype.shiftDownwards=!0;mxSpaceManager.prototype.extendParents=!0;mxSpaceManager.prototype.resizeHandler=null;mxSpaceManager.prototype.foldHandler=null;mxSpaceManager.prototype.isCellIgnored=function(a){return!this.getGraph().getModel().isVertex(a)};mxSpaceManager.prototype.isCellShiftable=function(a){return this.getGraph().getModel().isVertex(a)&&this.getGraph().isCellMovable(a)};
-mxSpaceManager.prototype.isEnabled=function(){return this.enabled};mxSpaceManager.prototype.setEnabled=function(a){this.enabled=a};mxSpaceManager.prototype.isShiftRightwards=function(){return this.shiftRightwards};mxSpaceManager.prototype.setShiftRightwards=function(a){this.shiftRightwards=a};mxSpaceManager.prototype.isShiftDownwards=function(){return this.shiftDownwards};mxSpaceManager.prototype.setShiftDownwards=function(a){this.shiftDownwards=a};mxSpaceManager.prototype.isExtendParents=function(){return this.extendParents};
-mxSpaceManager.prototype.setExtendParents=function(a){this.extendParents=a};mxSpaceManager.prototype.getGraph=function(){return this.graph};mxSpaceManager.prototype.setGraph=function(a){if(this.graph!=null){this.graph.removeListener(this.resizeHandler);this.graph.removeListener(this.foldHandler)}this.graph=a;if(this.graph!=null){this.graph.addListener(mxEvent.RESIZE_CELLS,this.resizeHandler);this.graph.addListener(mxEvent.FOLD_CELLS,this.foldHandler)}};
-mxSpaceManager.prototype.cellsResized=function(a){if(a!=null){var b=this.graph.getModel();b.beginUpdate();try{for(var c=0;c<a.length;c++)if(!this.isCellIgnored(a[c])){this.cellResized(a[c]);break}}finally{b.endUpdate()}}};
-mxSpaceManager.prototype.cellResized=function(a){var b=this.getGraph(),c=b.getView(),d=b.getModel(),e=c.getState(a),f=c.getState(d.getParent(a));if(e!=null&&f!=null){var g=this.getCellsToShift(e),h=d.getGeometry(a);if(g!=null&&h!=null){var k=c.translate,i=c.scale,c=e.x-f.origin.x-k.x*i,f=e.y-f.origin.y-k.y*i,k=e.x+e.width,l=e.y+e.height,m=e.width-h.width*i+c-h.x*i,n=e.height-h.height*i+f-h.y*i,o=1-h.width*i/e.width,e=1-h.height*i/e.height;d.beginUpdate();try{for(h=0;h<g.length;h++)g[h]!=a&&this.isCellShiftable(g[h])&&
-this.shiftCell(g[h],m,n,c,f,k,l,o,e,this.isExtendParents()&&b.isExtendParent(g[h]))}finally{d.endUpdate()}}}};
-mxSpaceManager.prototype.shiftCell=function(a,b,c,d,e,f,g,h,k,i){var d=this.getGraph(),l=d.getView().getState(a);if(l!=null){var m=d.getModel(),n=m.getGeometry(a);if(n!=null){m.beginUpdate();try{if(this.isShiftRightwards())if(l.x>=f){n=n.clone();n.translate(-b,0)}else{var o=Math.max(0,l.x-x0),n=n.clone();n.translate(-h*o,0)}if(this.isShiftDownwards())if(l.y>=g){n=n.clone();n.translate(0,-c)}else{var p=Math.max(0,l.y-e),n=n.clone();n.translate(0,-k*p)}if(n!=m.getGeometry(a)){m.setGeometry(a,n);i&&
-d.extendParent(a)}}finally{m.endUpdate()}}}};mxSpaceManager.prototype.getCellsToShift=function(a){var b=this.getGraph(),c=b.getModel().getParent(a.cell),d=this.isShiftDownwards(),e=this.isShiftRightwards();return b.getCellsBeyond(a.x+(d?0:a.width),a.y+(d&&e?0:a.height),c,e,d)};mxSpaceManager.prototype.destroy=function(){this.setGraph(null)};
-function mxSwimlaneManager(a,b,c,d){this.horizontal=b!=null?b:true;this.addEnabled=c!=null?c:true;this.resizeEnabled=d!=null?d:true;this.addHandler=mxUtils.bind(this,function(a,b){this.isEnabled()&&this.isAddEnabled()&&this.cellsAdded(b.getProperty("cells"))});this.resizeHandler=mxUtils.bind(this,function(a,b){this.isEnabled()&&this.isResizeEnabled()&&this.cellsResized(b.getProperty("cells"))});this.setGraph(a)}mxSwimlaneManager.prototype=new mxEventSource;
-mxSwimlaneManager.prototype.constructor=mxSwimlaneManager;mxSwimlaneManager.prototype.graph=null;mxSwimlaneManager.prototype.enabled=!0;mxSwimlaneManager.prototype.horizontal=!0;mxSwimlaneManager.prototype.addEnabled=!0;mxSwimlaneManager.prototype.resizeEnabled=!0;mxSwimlaneManager.prototype.addHandler=null;mxSwimlaneManager.prototype.resizeHandler=null;mxSwimlaneManager.prototype.isEnabled=function(){return this.enabled};mxSwimlaneManager.prototype.setEnabled=function(a){this.enabled=a};
-mxSwimlaneManager.prototype.isHorizontal=function(){return this.horizontal};mxSwimlaneManager.prototype.setHorizontal=function(a){this.horizontal=a};mxSwimlaneManager.prototype.isAddEnabled=function(){return this.addEnabled};mxSwimlaneManager.prototype.setAddEnabled=function(a){this.addEnabled=a};mxSwimlaneManager.prototype.isResizeEnabled=function(){return this.resizeEnabled};mxSwimlaneManager.prototype.setResizeEnabled=function(a){this.resizeEnabled=a};mxSwimlaneManager.prototype.getGraph=function(){return this.graph};
-mxSwimlaneManager.prototype.setGraph=function(a){if(this.graph!=null){this.graph.removeListener(this.addHandler);this.graph.removeListener(this.resizeHandler)}this.graph=a;if(this.graph!=null){this.graph.addListener(mxEvent.ADD_CELLS,this.addHandler);this.graph.addListener(mxEvent.CELLS_RESIZED,this.resizeHandler)}};mxSwimlaneManager.prototype.isSwimlaneIgnored=function(a){return!this.getGraph().isSwimlane(a)};
-mxSwimlaneManager.prototype.isCellHorizontal=function(a){if(this.graph.isSwimlane(a)){var b=this.graph.view.getState(a),a=b!=null?b.style:this.graph.getCellStyle(a);return mxUtils.getValue(a,mxConstants.STYLE_HORIZONTAL,1)==1}return!this.isHorizontal()};mxSwimlaneManager.prototype.cellsAdded=function(a){if(a!=null){var b=this.getGraph().getModel();b.beginUpdate();try{for(var c=0;c<a.length;c++)this.isSwimlaneIgnored(a[c])||this.swimlaneAdded(a[c])}finally{b.endUpdate()}}};
-mxSwimlaneManager.prototype.swimlaneAdded=function(a){for(var b=this.getGraph().getModel(),c=b.getParent(a),d=b.getChildCount(c),e=null,f=0;f<d;f++){var g=b.getChildAt(c,f);if(g!=a&&!this.isSwimlaneIgnored(g)){e=b.getGeometry(g);if(e!=null)break}}e!=null&&this.resizeSwimlane(a,e.width,e.height)};
-mxSwimlaneManager.prototype.cellsResized=function(a){if(a!=null){var b=this.getGraph().getModel();b.beginUpdate();try{for(var c=0;c<a.length;c++)if(!this.isSwimlaneIgnored(a[c])){var d=b.getGeometry(a[c]);if(d!=null){for(var e=new mxRectangle(0,0,d.width,d.height),f=a[c],g=f;g!=null;){var f=g,g=b.getParent(g),h=this.graph.isSwimlane(g)?this.graph.getStartSize(g):new mxRectangle;e.width=e.width+h.width;e.height=e.height+h.height}this.resizeSwimlane(f,e.width,e.height)}}}finally{b.endUpdate()}}};
-mxSwimlaneManager.prototype.resizeSwimlane=function(a,b,c){var d=this.getGraph().getModel();d.beginUpdate();try{if(!this.isSwimlaneIgnored(a)){var e=d.getGeometry(a);if(e!=null){var f=this.isCellHorizontal(a);if(f&&e.height!=c||!f&&e.width!=b){e=e.clone();f?e.height=c:e.width=b;d.setGeometry(a,e)}}}for(var g=this.graph.isSwimlane(a)?this.graph.getStartSize(a):new mxRectangle,b=b-g.width,c=c-g.height,h=d.getChildCount(a),e=0;e<h;e++)this.resizeSwimlane(d.getChildAt(a,e),b,c)}finally{d.endUpdate()}};
-mxSwimlaneManager.prototype.destroy=function(){this.setGraph(null)};function mxTemporaryCellStates(a,b,c){this.view=a;b=b!=null?b:1;this.oldBounds=a.getGraphBounds();this.oldStates=a.getStates();this.oldScale=a.getScale();a.setStates(new mxDictionary);a.setScale(b);if(c!=null){for(var b=a.createState(new mxCell),d=0;d<c.length;d++)a.validateBounds(b,c[d]);for(var e=null,d=0;d<c.length;d++){var f=a.validatePoints(b,c[d]);e==null?e=f:e.add(f)}e==null&&(e=new mxRectangle);a.setGraphBounds(e)}}
-mxTemporaryCellStates.prototype.view=null;mxTemporaryCellStates.prototype.oldStates=null;mxTemporaryCellStates.prototype.oldBounds=null;mxTemporaryCellStates.prototype.oldScale=null;mxTemporaryCellStates.prototype.destroy=function(){this.view.setScale(this.oldScale);this.view.setStates(this.oldStates);this.view.setGraphBounds(this.oldBounds)};function mxCellStatePreview(a){this.graph=a;this.deltas={}}mxCellStatePreview.prototype.graph=null;mxCellStatePreview.prototype.deltas=null;
-mxCellStatePreview.prototype.count=0;mxCellStatePreview.prototype.isEmpty=function(){return this.count==0};mxCellStatePreview.prototype.moveState=function(a,b,c,d,e){var d=d!=null?d:true,e=e!=null?e:true,f=mxCellPath.create(a.cell),g=this.deltas[f];if(g==null){g=new mxPoint(b,c);this.deltas[f]=g;this.count++}else if(d){g.X=g.X+b;g.Y=g.Y+c}else{g.X=b;g.Y=c}e&&this.addEdges(a);return g};
-mxCellStatePreview.prototype.show=function(a){var b=this.graph.getModel(),c=b.getRoot(),d;for(d in this.deltas){var e=mxCellPath.resolve(c,d),f=this.graph.view.getState(e),g=this.deltas[d],e=this.graph.view.getState(b.getParent(e));this.translateState(e,f,g.x,g.y)}for(d in this.deltas){e=mxCellPath.resolve(c,d);f=this.graph.view.getState(e);g=this.deltas[d];e=this.graph.view.getState(b.getParent(e));this.revalidateState(e,f,g.x,g.y,a)}};
-mxCellStatePreview.prototype.translateState=function(a,b,c,d){if(b!=null){var e=this.graph.getModel();if(e.isVertex(b.cell)){b.invalid=true;this.graph.view.validateBounds(a,b.cell);var a=e.getGeometry(b.cell),f=mxCellPath.create(b.cell);if((c!=0||d!=0)&&a!=null&&(!a.relative||this.deltas[f]!=null)){b.x=b.x+c;b.y=b.y+d}}a=e.getChildCount(b.cell);for(f=0;f<a;f++)this.translateState(b,this.graph.view.getState(e.getChildAt(b.cell,f)),c,d)}};
-mxCellStatePreview.prototype.revalidateState=function(a,b,c,d,e){if(b!=null){b.invalid=true;this.graph.view.validatePoints(a,b.cell);var f=mxCellPath.create(b.cell),g=this.graph.getModel(),h=this.graph.getCellGeometry(b.cell);if((c!=0||d!=0)&&h!=null&&h.relative&&g.isVertex(b.cell)&&(a==null||g.isVertex(a.cell)||this.deltas[f]!=null)){b.x=b.x+c;b.y=b.y+d;this.graph.cellRenderer.redraw(b)}e!=null&&e(b);a=g.getChildCount(b.cell);for(f=0;f<a;f++)this.revalidateState(b,this.graph.view.getState(g.getChildAt(b.cell,
-f)),c,d,e)}};mxCellStatePreview.prototype.addEdges=function(a){for(var b=this.graph.getModel(),c=b.getEdgeCount(a.cell),d=0;d<c;d++){var e=this.graph.view.getState(b.getEdgeAt(a.cell,d));e!=null&&this.moveState(e,0,0)}};function mxConnectionConstraint(a,b){this.point=a;this.perimeter=b!=null?b:true}mxConnectionConstraint.prototype.point=null;mxConnectionConstraint.prototype.perimeter=null;
-function mxGraphHandler(a){this.graph=a;this.graph.addMouseListener(this);this.panHandler=mxUtils.bind(this,function(){this.updatePreviewShape()});this.graph.addListener(mxEvent.PAN,this.panHandler)}mxGraphHandler.prototype.graph=null;mxGraphHandler.prototype.maxCells=mxClient.IS_IE?20:50;mxGraphHandler.prototype.enabled=!0;mxGraphHandler.prototype.highlightEnabled=!0;mxGraphHandler.prototype.cloneEnabled=!0;mxGraphHandler.prototype.moveEnabled=!0;mxGraphHandler.prototype.guidesEnabled=!1;
-mxGraphHandler.prototype.guide=null;mxGraphHandler.prototype.currentDx=null;mxGraphHandler.prototype.currentDy=null;mxGraphHandler.prototype.updateCursor=!0;mxGraphHandler.prototype.selectEnabled=!0;mxGraphHandler.prototype.removeCellsFromParent=!0;mxGraphHandler.prototype.connectOnDrop=!1;mxGraphHandler.prototype.scrollOnMove=!0;mxGraphHandler.prototype.minimumSize=6;mxGraphHandler.prototype.previewColor="black";mxGraphHandler.prototype.htmlPreview=!1;mxGraphHandler.prototype.shape=null;
-mxGraphHandler.prototype.scaleGrid=!1;mxGraphHandler.prototype.crisp=!0;mxGraphHandler.prototype.isEnabled=function(){return this.enabled};mxGraphHandler.prototype.setEnabled=function(a){this.enabled=a};mxGraphHandler.prototype.isCloneEnabled=function(){return this.cloneEnabled};mxGraphHandler.prototype.setCloneEnabled=function(a){this.cloneEnabled=a};mxGraphHandler.prototype.isMoveEnabled=function(){return this.moveEnabled};mxGraphHandler.prototype.setMoveEnabled=function(a){this.moveEnabled=a};
-mxGraphHandler.prototype.isSelectEnabled=function(){return this.selectEnabled};mxGraphHandler.prototype.setSelectEnabled=function(a){this.selectEnabled=a};mxGraphHandler.prototype.isRemoveCellsFromParent=function(){return this.removeCellsFromParent};mxGraphHandler.prototype.setRemoveCellsFromParent=function(a){this.removeCellsFromParent=a};mxGraphHandler.prototype.getInitialCellForEvent=function(a){return a.getCell()};mxGraphHandler.prototype.isDelayedSelection=function(a){return this.graph.isCellSelected(a)};
-mxGraphHandler.prototype.mouseDown=function(a,b){if(!b.isConsumed()&&this.isEnabled()&&this.graph.isEnabled()&&!this.graph.isForceMarqueeEvent(b.getEvent())&&b.getState()!=null){var c=this.getInitialCellForEvent(b);this.cell=null;this.delayedSelection=this.isDelayedSelection(c);this.isSelectEnabled()&&!this.delayedSelection&&this.graph.selectCellForEvent(c,b.getEvent());if(this.isMoveEnabled()){var d=this.graph.model,e=d.getGeometry(c);this.graph.isCellMovable(c)&&(!d.isEdge(c)||this.graph.getSelectionCount()>
-1||e.points!=null&&e.points.length>0||d.getTerminal(c,true)==null||d.getTerminal(c,false)==null||this.graph.allowDanglingEdges||this.graph.isCloneEvent(b.getEvent())&&this.graph.isCellsCloneable())&&this.start(c,b.getX(),b.getY());this.cellWasClicked=true;if(!mxClient.IS_SF&&!mxClient.IS_GC||b.getSource().nodeName!="SELECT")b.consume();else if(mxClient.IS_SF&&b.getSource().nodeName=="SELECT"){this.cellWasClicked=false;this.first=null}}}};
-mxGraphHandler.prototype.getGuideStates=function(){var a=this.graph.getDefaultParent(),b=this.graph.getModel(),c=mxUtils.bind(this,function(a){return this.graph.view.getState(a)!=null&&b.isVertex(a)&&b.getGeometry(a)!=null&&!b.getGeometry(a).relative});return this.graph.view.getCellStates(b.filterDescendants(c,a))};mxGraphHandler.prototype.getCells=function(a){return!this.delayedSelection&&this.graph.isCellMovable(a)?[a]:this.graph.getMovableCells(this.graph.getSelectionCells())};
-mxGraphHandler.prototype.getPreviewBounds=function(a){a=this.graph.getView().getBounds(a);if(a!=null){if(a.width<this.minimumSize){a.x=a.x-(this.minimumSize-a.width)/2;a.width=this.minimumSize}if(a.height<this.minimumSize){a.y=a.y-(this.minimumSize-a.height)/2;a.height=this.minimumSize}}return a};
-mxGraphHandler.prototype.createPreviewShape=function(a){a=new mxRectangleShape(a,null,this.previewColor);a.isDashed=true;a.crisp=this.crisp;if(this.htmlPreview){a.dialect=mxConstants.DIALECT_STRICTHTML;a.init(this.graph.container)}else{a.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;a.init(this.graph.getView().getOverlayPane());a.dialect==mxConstants.DIALECT_SVG?a.node.setAttribute("style","pointer-events:none;"):a.node.style.background=""}return a};
-mxGraphHandler.prototype.start=function(a,b,c){this.cell=a;this.first=mxUtils.convertPoint(this.graph.container,b,c);this.cells=this.getCells(this.cell);this.bounds=this.getPreviewBounds(this.cells);if(this.guidesEnabled)this.guide=new mxGuide(this.graph,this.getGuideStates())};mxGraphHandler.prototype.useGuidesForEvent=function(a){return this.guide!=null?this.guide.isEnabledForEvent(a.getEvent()):true};
-mxGraphHandler.prototype.snap=function(a){var b=this.scaleGrid?this.graph.view.scale:1;a.x=this.graph.snap(a.x/b)*b;a.y=this.graph.snap(a.y/b)*b;return a};
-mxGraphHandler.prototype.mouseMove=function(a,b){var c=this.graph;if(!b.isConsumed()&&c.isMouseDown&&this.cell!=null&&this.first!=null&&this.bounds!=null){var d=mxUtils.convertPoint(c.container,b.getX(),b.getY()),e=d.x-this.first.x,f=d.y-this.first.y,d=c.tolerance;if(this.shape!=null||Math.abs(e)>d||Math.abs(f)>d){if(this.highlight==null)this.highlight=new mxCellHighlight(this.graph,mxConstants.DROP_TARGET_COLOR,3);if(this.shape==null)this.shape=this.createPreviewShape(this.bounds);var g=c.isGridEnabledEvent(b.getEvent()),
-d=true;if(this.guide!=null&&this.useGuidesForEvent(b)){f=this.guide.move(this.bounds,new mxPoint(e,f),g);d=false;e=f.x;f=f.y}else if(g)var h=c.getView().translate,k=c.getView().scale,g=this.bounds.x-(c.snap(this.bounds.x/k-h.x)+h.x)*k,h=this.bounds.y-(c.snap(this.bounds.y/k-h.y)+h.y)*k,f=this.snap(new mxPoint(e,f)),e=f.x-g,f=f.y-h;this.guide!=null&&d&&this.guide.hide();c.isConstrainedEvent(b.getEvent())&&(Math.abs(e)>Math.abs(f)?f=0:e=0);this.currentDx=e;this.currentDy=f;this.updatePreviewShape();
-e=null;d=b.getCell();c.isDropEnabled()&&this.highlightEnabled&&(e=c.getDropTarget(this.cells,b.getEvent(),d));f=e;for(g=c.getModel();f!=null&&f!=this.cells[0];)f=g.getParent(f);var h=c.isCloneEvent(b.getEvent())&&c.isCellsCloneable()&&this.isCloneEnabled(),k=c.getView().getState(e),i=false;if(k!=null&&f==null&&(g.getParent(this.cell)!=e||h)){if(this.target!=e){this.target=e;this.setHighlightColor(mxConstants.DROP_TARGET_COLOR)}i=true}else{this.target=null;if(this.connectOnDrop&&d!=null&&this.cells.length==
-1&&c.getModel().isVertex(d)&&c.isCellConnectable(d)){k=c.getView().getState(d);if(k!=null){this.setHighlightColor(c.getEdgeValidationError(null,this.cell,d)==null?mxConstants.VALID_COLOR:mxConstants.INVALID_CONNECT_TARGET_COLOR);i=true}}}k!=null&&i?this.highlight.highlight(k):this.highlight.hide()}b.consume();mxEvent.consume(b.getEvent())}else if((this.isMoveEnabled()||this.isCloneEnabled())&&this.updateCursor&&!b.isConsumed()&&b.getState()!=null&&!c.isMouseDown){e=c.getCursorForCell(b.getCell());
-e==null&&(c.isEnabled()&&c.isCellMovable(b.getCell()))&&(e=c.getModel().isEdge(b.getCell())?mxConstants.CURSOR_MOVABLE_EDGE:mxConstants.CURSOR_MOVABLE_VERTEX);b.getState().setCursor(e);b.consume()}};mxGraphHandler.prototype.updatePreviewShape=function(){if(this.shape!=null){this.shape.bounds=new mxRectangle(this.bounds.x+this.currentDx-this.graph.panDx,this.bounds.y+this.currentDy-this.graph.panDy,this.bounds.width,this.bounds.height);this.shape.redraw()}};
-mxGraphHandler.prototype.setHighlightColor=function(a){this.highlight!=null&&this.highlight.setHighlightColor(a)};
-mxGraphHandler.prototype.mouseUp=function(a,b){if(!b.isConsumed()){var c=this.graph;if(this.cell!=null&&this.first!=null&&this.shape!=null&&this.currentDx!=null&&this.currentDy!=null){var d=c.getView().scale,e=c.isCloneEvent(b.getEvent())&&c.isCellsCloneable()&&this.isCloneEnabled(),f=this.currentDx/d,d=this.currentDy/d,g=b.getCell();if(this.connectOnDrop&&this.target==null&&g!=null&&c.getModel().isVertex(g)&&c.isCellConnectable(g)&&c.isEdgeValid(null,this.cell,g))c.connectionHandler.connect(this.cell,
-g,b.getEvent());else{g=this.target;c.isSplitEnabled()&&c.isSplitTarget(g,this.cells,b.getEvent())?c.splitEdge(g,this.cells,null,f,d):this.moveCells(this.cells,f,d,e,this.target,b.getEvent())}}else this.isSelectEnabled()&&(this.delayedSelection&&this.cell!=null)&&this.selectDelayed(b)}this.cellWasClicked&&b.consume();this.reset()};mxGraphHandler.prototype.selectDelayed=function(a){this.graph.selectCellForEvent(this.cell,a.getEvent())};
-mxGraphHandler.prototype.reset=function(){this.destroyShapes();this.delayedSelection=this.cellWasClicked=false;this.target=this.cell=this.first=this.guides=this.currentDy=this.currentDx=null};mxGraphHandler.prototype.shouldRemoveCellsFromParent=function(a,b,c){if(this.graph.getModel().isVertex(a)){a=this.graph.getView().getState(a);c=mxUtils.convertPoint(this.graph.container,mxEvent.getClientX(c),mxEvent.getClientY(c));return a!=null&&!mxUtils.contains(a,c.x,c.y)}return false};
-mxGraphHandler.prototype.moveCells=function(a,b,c,d,e,f){d&&(a=this.graph.getCloneableCells(a));e==null&&(this.isRemoveCellsFromParent()&&this.shouldRemoveCellsFromParent(this.graph.getModel().getParent(this.cell),a,f))&&(e=this.graph.getDefaultParent());a=this.graph.moveCells(a,b-this.graph.panDx/this.graph.view.scale,c-this.graph.panDy/this.graph.view.scale,d,e,f);this.isSelectEnabled()&&this.scrollOnMove&&this.graph.scrollCellToVisible(a[0]);d&&this.graph.setSelectionCells(a)};
-mxGraphHandler.prototype.destroyShapes=function(){if(this.shape!=null){this.shape.destroy();this.shape=null}if(this.guide!=null){this.guide.destroy();this.guide=null}if(this.highlight!=null){this.highlight.destroy();this.highlight=null}};mxGraphHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);this.graph.removeListener(this.panHandler);this.destroyShapes()};
-function mxPanningHandler(a,b){if(a!=null){this.graph=a;this.factoryMethod=b;this.graph.addMouseListener(this);this.init()}}mxPanningHandler.prototype=new mxPopupMenu;mxPanningHandler.prototype.constructor=mxPanningHandler;mxPanningHandler.prototype.graph=null;mxPanningHandler.prototype.usePopupTrigger=!0;mxPanningHandler.prototype.useLeftButtonForPanning=!1;mxPanningHandler.prototype.selectOnPopup=!0;mxPanningHandler.prototype.clearSelectionOnBackground=!0;mxPanningHandler.prototype.ignoreCell=!1;
-mxPanningHandler.prototype.previewEnabled=!0;mxPanningHandler.prototype.useGrid=!1;mxPanningHandler.prototype.panningEnabled=!0;mxPanningHandler.prototype.isPanningEnabled=function(){return this.panningEnabled};mxPanningHandler.prototype.setPanningEnabled=function(a){this.panningEnabled=a};mxPanningHandler.prototype.init=function(){mxPopupMenu.prototype.init.apply(this);mxEvent.addListener(this.div,mxClient.IS_TOUCH?"touchmove":"mousemove",mxUtils.bind(this,function(){this.graph.tooltipHandler.hide()}))};
-mxPanningHandler.prototype.isPanningTrigger=function(a){var b=a.getEvent();return this.useLeftButtonForPanning&&(this.ignoreCell||a.getState()==null)&&mxEvent.isLeftMouseButton(b)||mxEvent.isControlDown(b)&&mxEvent.isShiftDown(b)||this.usePopupTrigger&&mxEvent.isPopupTrigger(b)};
-mxPanningHandler.prototype.mouseDown=function(a,b){if(!b.isConsumed()&&this.isEnabled()){this.hideMenu();this.dx0=-this.graph.container.scrollLeft;this.dy0=-this.graph.container.scrollTop;this.popupTrigger=this.isPopupTrigger(b);this.panningTrigger=this.isPanningEnabled()&&this.isPanningTrigger(b);this.startX=b.getX();this.startY=b.getY();this.panningTrigger&&this.consumePanningTrigger(b)}};mxPanningHandler.prototype.consumePanningTrigger=function(a){a.consume()};
-mxPanningHandler.prototype.mouseMove=function(a,b){var c=b.getX()-this.startX,d=b.getY()-this.startY;if(this.active){if(this.previewEnabled){if(this.useGrid){c=this.graph.snap(c);d=this.graph.snap(d)}this.graph.panGraph(c+this.dx0,d+this.dy0)}this.fireEvent(new mxEventObject(mxEvent.PAN,"event",b));b.consume()}else if(this.panningTrigger){var e=this.active;this.active=Math.abs(c)>this.graph.tolerance||Math.abs(d)>this.graph.tolerance;!e&&this.active&&this.fireEvent(new mxEventObject(mxEvent.PAN_START,
-"event",b))}};
-mxPanningHandler.prototype.mouseUp=function(a,b){var c=Math.abs(b.getX()-this.startX),d=Math.abs(b.getY()-this.startY);if(this.active){if(!this.graph.useScrollbarsForPanning||!mxUtils.hasScrollbars(this.graph.container)){c=b.getX()-this.startX;d=b.getY()-this.startY;if(this.useGrid){c=this.graph.snap(c);d=this.graph.snap(d)}var e=this.graph.getView().scale,f=this.graph.getView().translate;this.graph.panGraph(0,0);this.panGraph(f.x+c/e,f.y+d/e)}this.active=false;this.fireEvent(new mxEventObject(mxEvent.PAN_END,"event",
-b));b.consume()}else if(this.popupTrigger&&c<this.graph.tolerance&&d<this.graph.tolerance){c=this.getCellForPopupEvent(b);this.graph.isEnabled()&&this.selectOnPopup&&c!=null&&!this.graph.isCellSelected(c)?this.graph.setSelectionCell(c):this.clearSelectionOnBackground&&c==null&&this.graph.clearSelection();this.graph.tooltipHandler.hide();d=mxUtils.getScrollOrigin();d=new mxPoint(b.getX()+d.x,b.getY()+d.y);this.popup(d.x+1,d.y+1,c,b.getEvent());b.consume()}this.popupTrigger=this.panningTrigger=false};
-mxPanningHandler.prototype.getCellForPopupEvent=function(a){return a.getCell()};mxPanningHandler.prototype.panGraph=function(a,b){this.graph.getView().setTranslate(a,b)};mxPanningHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);mxPopupMenu.prototype.destroy.apply(this)};
-function mxCellMarker(a,b,c,d){if(a!=null){this.graph=a;this.validColor=b!=null?b:mxConstants.DEFAULT_VALID_COLOR;this.invalidColor=b!=null?c:mxConstants.DEFAULT_INVALID_COLOR;this.hotspot=d!=null?d:mxConstants.DEFAULT_HOTSPOT;this.highlight=new mxCellHighlight(a)}}mxCellMarker.prototype=new mxEventSource;mxCellMarker.prototype.constructor=mxCellMarker;mxCellMarker.prototype.graph=null;mxCellMarker.prototype.enabled=!0;mxCellMarker.prototype.hotspot=mxConstants.DEFAULT_HOTSPOT;
-mxCellMarker.prototype.hotspotEnabled=!1;mxCellMarker.prototype.validColor=null;mxCellMarker.prototype.invalidColor=null;mxCellMarker.prototype.currentColor=null;mxCellMarker.prototype.validState=null;mxCellMarker.prototype.markedState=null;mxCellMarker.prototype.setEnabled=function(a){this.enabled=a};mxCellMarker.prototype.isEnabled=function(){return this.enabled};mxCellMarker.prototype.setHotspot=function(a){this.hotspot=a};mxCellMarker.prototype.getHotspot=function(){return this.hotspot};
-mxCellMarker.prototype.setHotspotEnabled=function(a){this.hotspotEnabled=a};mxCellMarker.prototype.isHotspotEnabled=function(){return this.hotspotEnabled};mxCellMarker.prototype.hasValidState=function(){return this.validState!=null};mxCellMarker.prototype.getValidState=function(){return this.validState};mxCellMarker.prototype.getMarkedState=function(){return this.markedState};mxCellMarker.prototype.reset=function(){this.validState=null;if(this.markedState!=null){this.markedState=null;this.unmark()}};
-mxCellMarker.prototype.process=function(a){var b=null;if(this.isEnabled()){var b=this.getState(a),c=b!=null?this.isValidState(b):false,a=this.getMarkerColor(a.getEvent(),b,c);this.validState=c?b:null;if(b!=this.markedState||a!=this.currentColor){this.currentColor=a;if(b!=null&&this.currentColor!=null){this.markedState=b;this.mark()}else if(this.markedState!=null){this.markedState=null;this.unmark()}}}return b};
-mxCellMarker.prototype.markCell=function(a,b){var c=this.graph.getView().getState(a);if(c!=null){this.currentColor=b!=null?b:this.validColor;this.markedState=c;this.mark()}};mxCellMarker.prototype.mark=function(){this.highlight.setHighlightColor(this.currentColor);this.highlight.highlight(this.markedState);this.fireEvent(new mxEventObject(mxEvent.MARK,"state",this.markedState))};mxCellMarker.prototype.unmark=function(){this.mark()};mxCellMarker.prototype.isValidState=function(){return true};
-mxCellMarker.prototype.getMarkerColor=function(a,b,c){return c?this.validColor:this.invalidColor};mxCellMarker.prototype.getState=function(a){var b=this.graph.getView();cell=this.getCell(a);b=this.getStateToMark(b.getState(cell));return b!=null&&this.intersects(b,a)?b:null};mxCellMarker.prototype.getCell=function(a){return a.getCell()};mxCellMarker.prototype.getStateToMark=function(a){return a};
-mxCellMarker.prototype.intersects=function(a,b){return this.hotspotEnabled?mxUtils.intersectsHotspot(a,b.getGraphX(),b.getGraphY(),this.hotspot,mxConstants.MIN_HOTSPOT_SIZE,mxConstants.MAX_HOTSPOT_SIZE):true};mxCellMarker.prototype.destroy=function(){this.graph.getView().removeListener(this.resetHandler);this.graph.getModel().removeListener(this.resetHandler);this.highlight.destroy()};
-function mxSelectionCellsHandler(a){this.graph=a;this.handlers=new mxDictionary;this.graph.addMouseListener(this);this.refreshHandler=mxUtils.bind(this,function(){this.isEnabled()&&this.refresh()});this.graph.getSelectionModel().addListener(mxEvent.CHANGE,this.refreshHandler);this.graph.getModel().addListener(mxEvent.CHANGE,this.refreshHandler);this.graph.getView().addListener(mxEvent.SCALE,this.refreshHandler);this.graph.getView().addListener(mxEvent.TRANSLATE,this.refreshHandler);this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE,
-this.refreshHandler);this.graph.getView().addListener(mxEvent.DOWN,this.refreshHandler);this.graph.getView().addListener(mxEvent.UP,this.refreshHandler)}mxSelectionCellsHandler.prototype=new mxEventSource;mxSelectionCellsHandler.prototype.constructor=mxSelectionCellsHandler;mxSelectionCellsHandler.prototype.graph=null;mxSelectionCellsHandler.prototype.enabled=!0;mxSelectionCellsHandler.prototype.refreshHandler=null;mxSelectionCellsHandler.prototype.maxHandlers=100;
-mxSelectionCellsHandler.prototype.handlers=null;mxSelectionCellsHandler.prototype.isEnabled=function(){return this.enabled};mxSelectionCellsHandler.prototype.setEnabled=function(a){this.enabled=a};mxSelectionCellsHandler.prototype.getHandler=function(a){return this.handlers.get(a)};mxSelectionCellsHandler.prototype.reset=function(){this.handlers.visit(function(a,b){b.reset.apply(b)})};
-mxSelectionCellsHandler.prototype.refresh=function(){var a=this.handlers;this.handlers=new mxDictionary;for(var b=this.graph.getSelectionCells(),c=0;c<b.length;c++){var d=this.graph.view.getState(b[c]);if(d!=null){var e=a.remove(b[c]);if(e!=null)if(e.state!=d){e.destroy();e=null}else e.redraw();if(e==null){e=this.graph.createHandler(d);this.fireEvent(new mxEventObject(mxEvent.ADD,"state",d))}e!=null&&this.handlers.put(b[c],e)}}a.visit(mxUtils.bind(this,function(a,b){this.fireEvent(new mxEventObject(mxEvent.REMOVE,
-"state",b.state));b.destroy()}))};mxSelectionCellsHandler.prototype.mouseDown=function(a,b){if(this.graph.isEnabled()&&this.isEnabled()){var c=[a,b];this.handlers.visit(function(a,b){b.mouseDown.apply(b,c)})}};mxSelectionCellsHandler.prototype.mouseMove=function(a,b){if(this.graph.isEnabled()&&this.isEnabled()){var c=[a,b];this.handlers.visit(function(a,b){b.mouseMove.apply(b,c)})}};
-mxSelectionCellsHandler.prototype.mouseUp=function(a,b){if(this.graph.isEnabled()&&this.isEnabled()){var c=[a,b];this.handlers.visit(function(a,b){b.mouseUp.apply(b,c)})}};mxSelectionCellsHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);if(this.refreshHandler!=null){this.graph.getSelectionModel().removeListener(this.refreshHandler);this.graph.getModel().removeListener(this.refreshHandler);this.graph.getView().removeListener(this.refreshHandler);this.refreshHandler=null}};
-function mxConnectionHandler(a,b){if(a!=null){this.graph=a;this.factoryMethod=b;this.init()}}mxConnectionHandler.prototype=new mxEventSource;mxConnectionHandler.prototype.constructor=mxConnectionHandler;mxConnectionHandler.prototype.graph=null;mxConnectionHandler.prototype.factoryMethod=!0;mxConnectionHandler.prototype.moveIconFront=!1;mxConnectionHandler.prototype.moveIconBack=!1;mxConnectionHandler.prototype.connectImage=null;mxConnectionHandler.prototype.targetConnectImage=!1;
-mxConnectionHandler.prototype.enabled=!0;mxConnectionHandler.prototype.select=!0;mxConnectionHandler.prototype.createTarget=!1;mxConnectionHandler.prototype.marker=null;mxConnectionHandler.prototype.constraintHandler=null;mxConnectionHandler.prototype.error=null;mxConnectionHandler.prototype.waypointsEnabled=!1;mxConnectionHandler.prototype.tapAndHoldEnabled=!0;mxConnectionHandler.prototype.tapAndHoldDelay=500;mxConnectionHandler.prototype.tapAndHoldInProgress=!1;
-mxConnectionHandler.prototype.tapAndHoldValid=!1;mxConnectionHandler.prototype.tapAndHoldTolerance=4;mxConnectionHandler.prototype.initialTouchX=0;mxConnectionHandler.prototype.initialTouchY=0;mxConnectionHandler.prototype.ignoreMouseDown=!1;mxConnectionHandler.prototype.first=null;mxConnectionHandler.prototype.connectIconOffset=new mxPoint(0,mxConstants.TOOLTIP_VERTICAL_OFFSET);mxConnectionHandler.prototype.edgeState=null;mxConnectionHandler.prototype.changeHandler=null;
-mxConnectionHandler.prototype.drillHandler=null;mxConnectionHandler.prototype.mouseDownCounter=0;mxConnectionHandler.prototype.movePreviewAway=mxClient.IS_VML;mxConnectionHandler.prototype.isEnabled=function(){return this.enabled};mxConnectionHandler.prototype.setEnabled=function(a){this.enabled=a};mxConnectionHandler.prototype.isCreateTarget=function(){return this.createTarget};mxConnectionHandler.prototype.setCreateTarget=function(a){this.createTarget=a};
-mxConnectionHandler.prototype.createShape=function(){var a=new mxPolyline([],mxConstants.INVALID_COLOR);a.isDashed=true;a.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;a.init(this.graph.getView().getOverlayPane());if(this.graph.dialect==mxConstants.DIALECT_SVG){a.pipe.setAttribute("style","pointer-events:none;");a.innerNode.setAttribute("style","pointer-events:none;")}else{var b=mxUtils.bind(this,function(a){a=mxUtils.convertPoint(this.graph.container,
-mxEvent.getClientX(a),mxEvent.getClientY(a));return this.graph.view.getState(this.graph.getCellAt(a.x,a.y))});mxEvent.redirectMouseEvents(a.node,this.graph,b)}return a};
-mxConnectionHandler.prototype.init=function(){this.graph.addMouseListener(this);this.marker=this.createMarker();this.constraintHandler=new mxConstraintHandler(this.graph);this.changeHandler=mxUtils.bind(this,function(){if(this.iconState!=null)this.iconState=this.graph.getView().getState(this.iconState.cell);if(this.iconState!=null)this.redrawIcons(this.icons,this.iconState);else{this.destroyIcons(this.icons);this.previous=null}this.constraintHandler.reset()});this.graph.getModel().addListener(mxEvent.CHANGE,
-this.changeHandler);this.graph.getView().addListener(mxEvent.SCALE,this.changeHandler);this.graph.getView().addListener(mxEvent.TRANSLATE,this.changeHandler);this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE,this.changeHandler);this.drillHandler=mxUtils.bind(this,function(){this.destroyIcons(this.icons)});this.graph.addListener(mxEvent.START_EDITING,this.drillHandler);this.graph.getView().addListener(mxEvent.DOWN,this.drillHandler);this.graph.getView().addListener(mxEvent.UP,this.drillHandler)};
-mxConnectionHandler.prototype.isConnectableCell=function(){return true};
-mxConnectionHandler.prototype.createMarker=function(){var a=new mxCellMarker(this.graph);a.hotspotEnabled=true;a.getCell=mxUtils.bind(this,function(b,c){c=mxCellMarker.prototype.getCell.apply(a,arguments);this.error=null;if(!this.isConnectableCell(c))return null;if(c!=null)if(this.isConnecting()){if(this.previous!=null){this.error=this.validateConnection(this.previous.cell,c);if(this.error!=null&&this.error.length==0){c=null;if(this.isCreateTarget())this.error=null}}}else this.isValidSource(c)||(c=
-null);else if(this.isConnecting()&&!this.isCreateTarget()&&!this.graph.allowDanglingEdges)this.error="";return c});a.isValidState=mxUtils.bind(this,function(b){return this.isConnecting()?this.error==null:mxCellMarker.prototype.isValidState.apply(a,arguments)});a.getMarkerColor=mxUtils.bind(this,function(b,c,d){return this.connectImage==null||this.isConnecting()?mxCellMarker.prototype.getMarkerColor.apply(a,arguments):null});a.intersects=mxUtils.bind(this,function(b,c){return this.connectImage!=null||
-this.isConnecting()?true:mxCellMarker.prototype.intersects.apply(a,arguments)});return a};mxConnectionHandler.prototype.start=function(a,b,c,d){this.previous=a;this.first=new mxPoint(b,c);this.edgeState=d!=null?d:this.createEdgeState(null);this.marker.currentColor=this.marker.validColor;this.marker.markedState=a;this.marker.mark();this.fireEvent(new mxEventObject(mxEvent.START,"state",this.previous))};mxConnectionHandler.prototype.isConnecting=function(){return this.first!=null&&this.shape!=null};
-mxConnectionHandler.prototype.isValidSource=function(a){return this.graph.isValidSource(a)};mxConnectionHandler.prototype.isValidTarget=function(){return true};mxConnectionHandler.prototype.validateConnection=function(a,b){return!this.isValidTarget(b)?"":this.graph.getEdgeValidationError(null,a,b)};mxConnectionHandler.prototype.getConnectImage=function(){return this.connectImage};
-mxConnectionHandler.prototype.isMoveIconToFrontForState=function(a){return a.text!=null&&a.text.node.parentNode==this.graph.container?true:this.moveIconFront};
-mxConnectionHandler.prototype.createIcons=function(a){var b=this.getConnectImage(a);if(b!=null&&a!=null){this.iconState=a;var c=[],d=new mxRectangle(0,0,b.width,b.height),e=new mxImageShape(d,b.src,null,null,0);e.preserveImageAspect=false;if(this.isMoveIconToFrontForState(a)){e.dialect=mxConstants.DIALECT_STRICTHTML;e.init(this.graph.container)}else{e.dialect=this.graph.dialect==mxConstants.DIALECT_SVG?mxConstants.DIALECT_SVG:mxConstants.DIALECT_VML;e.init(this.graph.getView().getOverlayPane());this.moveIconBack&&
-e.node.previousSibling!=null&&e.node.parentNode.insertBefore(e.node,e.node.parentNode.firstChild)}e.node.style.cursor=mxConstants.CURSOR_CONNECT;var f=mxUtils.bind(this,function(){return this.currentState!=null?this.currentState:a}),b=mxUtils.bind(this,function(a){if(!mxEvent.isConsumed(a)){this.icon=e;this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(a,f()))}});mxEvent.redirectMouseEvents(e.node,this.graph,f,b);c.push(e);this.redrawIcons(c,this.iconState);return c}return null};
-mxConnectionHandler.prototype.redrawIcons=function(a,b){if(a!=null&&a[0]!=null&&b!=null){var c=this.getIconPosition(a[0],b);a[0].bounds.x=c.x;a[0].bounds.y=c.y;a[0].redraw()}};mxConnectionHandler.prototype.getIconPosition=function(a,b){var c=this.graph.getView().scale,d=b.getCenterX(),e=b.getCenterY();if(this.graph.isSwimlane(b.cell))var f=this.graph.getStartSize(b.cell),d=f.width!=0?b.x+f.width*c/2:d,e=f.height!=0?b.y+f.height*c/2:e;return new mxPoint(d-a.bounds.width/2,e-a.bounds.height/2)};
-mxConnectionHandler.prototype.destroyIcons=function(a){if(a!=null){this.iconState=null;for(var b=0;b<a.length;b++)a[b].destroy()}};mxConnectionHandler.prototype.isStartEvent=function(a){return!this.graph.isForceMarqueeEvent(a.getEvent())&&(this.constraintHandler.currentFocus!=null&&this.constraintHandler.currentConstraint!=null||this.previous!=null&&this.error==null&&(this.icons==null||this.icons!=null&&this.icon!=null))};
-mxConnectionHandler.prototype.mouseDown=function(a,b){this.mouseDownCounter++;if(this.isEnabled()&&this.graph.isEnabled()&&!b.isConsumed()&&!this.isConnecting()&&this.isStartEvent(b)){if(this.constraintHandler.currentConstraint!=null&&this.constraintHandler.currentFocus!=null&&this.constraintHandler.currentPoint!=null){this.sourceConstraint=this.constraintHandler.currentConstraint;this.previous=this.constraintHandler.currentFocus;this.first=this.constraintHandler.currentPoint.clone()}else this.first=
-new mxPoint(b.getGraphX(),b.getGraphY());this.edgeState=this.createEdgeState(b);this.mouseDownCounter=1;if(this.waypointsEnabled&&this.shape==null){this.waypoints=null;this.shape=this.createShape()}this.previous==null&&this.edgeState!=null&&this.edgeState.cell.geometry.setTerminalPoint(this.graph.getPointForEvent(b.getEvent()),true);this.fireEvent(new mxEventObject(mxEvent.START,"state",this.previous));b.consume()}else if(mxClient.IS_TOUCH&&this.tapAndHoldEnabled&&!this.tapAndHoldInProgress&&this.isEnabled()&&
-this.graph.isEnabled()&&!this.isConnecting()){this.tapAndHoldInProgress=true;this.initialTouchX=b.getX();this.initialTouchY=b.getY();var c=this.graph.view.getState(this.marker.getCell(b));this.tapAndHoldThread&&window.clearTimeout(this.tapAndHoldThread);this.tapAndHoldThread=window.setTimeout(mxUtils.bind(this,function(){this.tapAndHoldValid&&this.tapAndHold(b,c);this.tapAndHoldValid=this.tapAndHoldInProgress=false}),this.tapAndHoldDelay);this.tapAndHoldValid=true}this.selectedIcon=this.icon;this.icon=
-null};mxConnectionHandler.prototype.tapAndHold=function(a,b){if(b!=null){this.marker.currentColor=this.marker.validColor;this.marker.markedState=b;this.marker.mark();this.first=new mxPoint(a.getGraphX(),a.getGraphY());this.edgeState=this.createEdgeState(a);this.previous=b;this.fireEvent(new mxEventObject(mxEvent.START,"state",this.previous))}};mxConnectionHandler.prototype.isImmediateConnectSource=function(a){return!this.graph.isCellMovable(a.cell)};mxConnectionHandler.prototype.createEdgeState=function(){return null};
-mxConnectionHandler.prototype.updateCurrentState=function(a){var b=this.marker.process(a);this.constraintHandler.update(a,this.first==null);this.currentState=b};mxConnectionHandler.prototype.convertWaypoint=function(a){var b=this.graph.getView().getScale(),c=this.graph.getView().getTranslate();a.x=a.x/b-c.x;a.y=a.y/b-c.y};
-mxConnectionHandler.prototype.mouseMove=function(a,b){if(this.tapAndHoldValid)this.tapAndHoldValid=Math.abs(this.initialTouchX-b.getX())<this.tapAndHoldTolerance&&Math.abs(this.initialTouchY-b.getY())<this.tapAndHoldTolerance;if(!b.isConsumed()&&(this.ignoreMouseDown||this.first!=null||!this.graph.isMouseDown)){if(!this.isEnabled()&&this.currentState!=null){this.destroyIcons(this.icons);this.currentState=null}(this.first!=null||this.isEnabled()&&this.graph.isEnabled())&&this.updateCurrentState(b);
-if(this.first!=null){var c=this.graph.getView().scale,c=new mxPoint(this.graph.snap(b.getGraphX()/c)*c,this.graph.snap(b.getGraphY()/c)*c),d=null,e=c;if(this.constraintHandler.currentConstraint!=null&&this.constraintHandler.currentFocus!=null&&this.constraintHandler.currentPoint!=null){d=this.constraintHandler.currentConstraint;e=this.constraintHandler.currentPoint.clone()}var f=this.first;if(this.selectedIcon!=null){var g=this.selectedIcon.bounds.width,h=this.selectedIcon.bounds.height;if(this.currentState!=
-null&&this.targetConnectImage){g=this.getIconPosition(this.selectedIcon,this.currentState);this.selectedIcon.bounds.x=g.x;this.selectedIcon.bounds.y=g.y}else this.selectedIcon.bounds=new mxRectangle(b.getGraphX()+this.connectIconOffset.x,b.getGraphY()+this.connectIconOffset.y,g,h);this.selectedIcon.redraw()}if(this.edgeState!=null){this.edgeState.absolutePoints=[null,this.currentState!=null?null:e];this.graph.view.updateFixedTerminalPoint(this.edgeState,this.previous,true,this.sourceConstraint);if(this.currentState!=
-null){d==null&&(d=this.graph.getConnectionConstraint(this.edgeState,this.previous,false));this.edgeState.setAbsoluteTerminalPoint(null,false);this.graph.view.updateFixedTerminalPoint(this.edgeState,this.currentState,false,d)}f=null;if(this.waypoints!=null){f=[];for(e=0;e<this.waypoints.length;e++){d=this.waypoints[e].clone();this.convertWaypoint(d);f[e]=d}}this.graph.view.updatePoints(this.edgeState,f,this.previous,this.currentState);this.graph.view.updateFloatingTerminalPoints(this.edgeState,this.previous,
-this.currentState);e=this.edgeState.absolutePoints[this.edgeState.absolutePoints.length-1];f=this.edgeState.absolutePoints[0]}else{if(this.currentState!=null&&this.constraintHandler.currentConstraint==null){g=this.getTargetPerimeterPoint(this.currentState,b);g!=null&&(e=g)}if(this.sourceConstraint==null&&this.previous!=null){g=this.getSourcePerimeterPoint(this.previous,this.waypoints!=null&&this.waypoints.length>0?this.waypoints[0]:e,b);g!=null&&(f=g)}}if(this.currentState==null&&this.movePreviewAway){g=
-f;if(this.edgeState!=null&&this.edgeState.absolutePoints.length>2){d=this.edgeState.absolutePoints[this.edgeState.absolutePoints.length-2];d!=null&&(g=d)}d=e.x-g.x;g=e.y-g.y;h=Math.sqrt(d*d+g*g);if(h==0)return;e.x=e.x-d*4/h;e.y=e.y-g*4/h}if(this.shape==null){d=Math.abs(c.x-this.first.x);g=Math.abs(c.y-this.first.y);if(d>this.graph.tolerance||g>this.graph.tolerance){this.shape=this.createShape();this.updateCurrentState(b)}}if(this.shape!=null){if(this.edgeState!=null)this.shape.points=this.edgeState.absolutePoints;
-else{c=[f];this.waypoints!=null&&(c=c.concat(this.waypoints));c.push(e);this.shape.points=c}this.drawPreview()}mxEvent.consume(b.getEvent());b.consume()}else if(!this.isEnabled()||!this.graph.isEnabled())this.constraintHandler.reset();else if(this.previous!=this.currentState&&this.edgeState==null){this.destroyIcons(this.icons);this.icons=null;if(this.currentState!=null&&this.error==null){this.icons=this.createIcons(this.currentState);if(this.icons==null){this.currentState.setCursor(mxConstants.CURSOR_CONNECT);
-b.consume()}}this.previous=this.currentState}else this.previous==this.currentState&&(this.currentState!=null&&this.icons==null&&!this.graph.isMouseDown)&&b.consume();this.constraintHandler.currentConstraint!=null&&this.marker.reset();if(!this.graph.isMouseDown&&this.currentState!=null&&this.icons!=null){c=false;f=b.getSource();for(e=0;e<this.icons.length&&!c;e++)c=f==this.icons[e].node||f.parentNode==this.icons[e].node;c||this.updateIcons(this.currentState,this.icons,b)}}else this.constraintHandler.reset()};
-mxConnectionHandler.prototype.getTargetPerimeterPoint=function(a){var b=null,c=a.view,d=c.getPerimeterFunction(a);if(d!=null){var e=this.waypoints!=null&&this.waypoints.length>0?this.waypoints[this.waypoints.length-1]:new mxPoint(this.previous.getCenterX(),this.previous.getCenterY()),a=d(c.getPerimeterBounds(a),this.edgeState,e,false);a!=null&&(b=a)}else b=new mxPoint(a.getCenterX(),a.getCenterY());return b};
-mxConnectionHandler.prototype.getSourcePerimeterPoint=function(a,b){var c=null,d=a.view,e=d.getPerimeterFunction(a);if(e!=null){d=e(d.getPerimeterBounds(a),a,b,false);d!=null&&(c=d)}else c=new mxPoint(a.getCenterX(),a.getCenterY());return c};mxConnectionHandler.prototype.updateIcons=function(){};mxConnectionHandler.prototype.isStopEvent=function(a){return a.getState()!=null};
-mxConnectionHandler.prototype.addWaypointForEvent=function(a){var b=mxUtils.convertPoint(this.graph.container,a.getX(),a.getY()),c=Math.abs(b.x-this.first.x),b=Math.abs(b.y-this.first.y);if(this.waypoints!=null||this.mouseDownCounter>1&&(c>this.graph.tolerance||b>this.graph.tolerance)){if(this.waypoints==null)this.waypoints=[];c=this.graph.view.scale;b=new mxPoint(this.graph.snap(a.getGraphX()/c)*c,this.graph.snap(a.getGraphY()/c)*c);this.waypoints.push(b)}};
-mxConnectionHandler.prototype.mouseUp=function(a,b){if(!b.isConsumed()&&this.isConnecting()){if(this.waypointsEnabled&&!this.isStopEvent(b)){this.addWaypointForEvent(b);b.consume();return}if(this.error==null){var c=this.previous!=null?this.previous.cell:null,d=null;if(this.constraintHandler.currentConstraint!=null&&this.constraintHandler.currentFocus!=null)d=this.constraintHandler.currentFocus.cell;if(d==null&&this.marker.hasValidState())d=this.marker.validState.cell;this.connect(c,d,b.getEvent(),
-b.getCell())}else{this.previous!=null&&(this.marker.validState!=null&&this.previous.cell==this.marker.validState.cell)&&this.graph.selectCellForEvent(this.marker.source,evt);this.error.length>0&&this.graph.validationAlert(this.error)}this.destroyIcons(this.icons);b.consume()}this.first!=null&&this.reset();this.tapAndHoldValid=this.tapAndHoldInProgress=false};
-mxConnectionHandler.prototype.reset=function(){if(this.shape!=null){this.shape.destroy();this.shape=null}this.destroyIcons(this.icons);this.icons=null;this.marker.reset();this.constraintHandler.reset();this.sourceConstraint=this.error=this.previous=this.edgeState=this.selectedIcon=null;this.mouseDownCounter=0;this.icon=this.first=null;this.fireEvent(new mxEventObject(mxEvent.RESET))};
-mxConnectionHandler.prototype.drawPreview=function(){var a=this.error==null,b=this.getEdgeColor(a);this.shape.dialect==mxConstants.DIALECT_SVG?this.shape.innerNode.setAttribute("stroke",b):this.shape.node.strokecolor=b;this.shape.strokewidth=this.getEdgeWidth(a);this.shape.redraw();mxUtils.repaintGraph(this.graph,this.shape.points[1])};mxConnectionHandler.prototype.getEdgeColor=function(a){return a?mxConstants.VALID_COLOR:mxConstants.INVALID_COLOR};
-mxConnectionHandler.prototype.getEdgeWidth=function(a){return a?3:1};
-mxConnectionHandler.prototype.connect=function(a,b,c,d){if(b!=null||this.isCreateTarget()||this.graph.allowDanglingEdges){var e=this.graph.getModel(),f=null;e.beginUpdate();try{if(a!=null&&b==null&&this.isCreateTarget()){b=this.createTargetVertex(c,a);if(b!=null){d=this.graph.getDropTarget([b],c,d);if(d==null||!this.graph.getModel().isEdge(d)){var g=this.graph.getView().getState(d);if(g!=null){var h=e.getGeometry(b);h.x=h.x-g.origin.x;h.y=h.y-g.origin.y}}else d=this.graph.getDefaultParent();this.graph.addCell(b,
-d)}}var k=this.graph.getDefaultParent();if(a!=null&&b!=null&&e.getParent(a)==e.getParent(b)&&e.getParent(e.getParent(a))!=e.getRoot()){k=e.getParent(a);a.geometry!=null&&a.geometry.relative&&(b.geometry!=null&&b.geometry.relative)&&(k=e.getParent(k))}h=g=null;if(this.edgeState!=null){g=this.edgeState.cell.value;h=this.edgeState.cell.style}f=this.insertEdge(k,null,g,a,b,h);if(f!=null){this.graph.setConnectionConstraint(f,a,true,this.sourceConstraint);this.graph.setConnectionConstraint(f,b,false,this.constraintHandler.currentConstraint);
-this.edgeState!=null&&e.setGeometry(f,this.edgeState.cell.geometry);var i=e.getGeometry(f);if(i==null){i=new mxGeometry;i.relative=true;e.setGeometry(f,i)}if(this.waypoints!=null&&this.waypoints.length>0){var l=this.graph.view.scale,m=this.graph.view.translate;i.points=[];for(a=0;a<this.waypoints.length;a++){var n=this.waypoints[a];i.points.push(new mxPoint(n.x/l-m.x,n.y/l-m.y))}}if(b==null){n=this.graph.getPointForEvent(c,false);n.x=n.x-this.graph.panDx/this.graph.view.scale;n.y=n.y-this.graph.panDy/
-this.graph.view.scale;i.setTerminalPoint(n,false)}this.fireEvent(new mxEventObject(mxEvent.CONNECT,"cell",f,"event",c,"target",d))}}catch(o){mxLog.show();mxLog.debug(o.message)}finally{e.endUpdate()}this.select&&this.selectCells(f,b)}};mxConnectionHandler.prototype.selectCells=function(a){this.graph.setSelectionCell(a)};
-mxConnectionHandler.prototype.insertEdge=function(a,b,c,d,e,f){if(this.factoryMethod==null)return this.graph.insertEdge(a,b,c,d,e,f);b=this.createEdge(c,d,e,f);return b=this.graph.addEdge(b,a,d,e)};
-mxConnectionHandler.prototype.createTargetVertex=function(a,b){for(var c=this.graph.getCellGeometry(b);c!=null&&c.relative;){b=this.graph.getModel().getParent(b);c=this.graph.getCellGeometry(b)}var d=this.graph.cloneCells([b])[0],c=this.graph.getModel().getGeometry(d);if(c!=null){var e=this.graph.getPointForEvent(a);c.x=this.graph.snap(e.x-c.width/2)-this.graph.panDx/this.graph.view.scale;c.y=this.graph.snap(e.y-c.height/2)-this.graph.panDy/this.graph.view.scale;if(this.first!=null){var f=this.graph.view.getState(b);
-if(f!=null){var g=this.getAlignmentTolerance();if(Math.abs(this.graph.snap(this.first.x)-this.graph.snap(e.x))<=g)c.x=f.x;else if(Math.abs(this.graph.snap(this.first.y)-this.graph.snap(e.y))<=g)c.y=f.y}}}return d};mxConnectionHandler.prototype.getAlignmentTolerance=function(){return this.graph.isGridEnabled()?this.graph.gridSize:this.graph.tolerance};
-mxConnectionHandler.prototype.createEdge=function(a,b,c,d){var e=null;this.factoryMethod!=null&&(e=this.factoryMethod(b,c,d));if(e==null){e=new mxCell(a||"");e.setEdge(true);e.setStyle(d);a=new mxGeometry;a.relative=true;e.setGeometry(a)}return e};
-mxConnectionHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);if(this.shape!=null){this.shape.destroy();this.shape=null}if(this.marker!=null){this.marker.destroy();this.marker=null}if(this.constraintHandler!=null){this.constraintHandler.destroy();this.constraintHandler=null}if(this.changeHandler!=null){this.graph.getModel().removeListener(this.changeHandler);this.graph.getView().removeListener(this.changeHandler);this.changeHandler=null}if(this.drillHandler!=null){this.graph.removeListener(this.drillHandler);
-this.graph.getView().removeListener(this.drillHandler);this.drillHandler=null}};function mxConstraintHandler(a){this.graph=a}mxConstraintHandler.prototype.pointImage=new mxImage(mxClient.imageBasePath+"/point.gif",5,5);mxConstraintHandler.prototype.graph=null;mxConstraintHandler.prototype.enabled=!0;mxConstraintHandler.prototype.highlightColor=mxConstants.DEFAULT_VALID_COLOR;mxConstraintHandler.prototype.isEnabled=function(){return this.enabled};
-mxConstraintHandler.prototype.setEnabled=function(a){this.enabled=a};mxConstraintHandler.prototype.reset=function(){if(this.focusIcons!=null){for(var a=0;a<this.focusIcons.length;a++)this.focusIcons[a].destroy();this.focusIcons=null}if(this.focusHighlight!=null){this.focusHighlight.destroy();this.focusHighlight=null}this.focusPoints=this.currentFocus=this.currentPoint=this.currentFocusArea=this.currentConstraint=null};mxConstraintHandler.prototype.getTolerance=function(){return this.graph.getTolerance()};
-mxConstraintHandler.prototype.getImageForConstraint=function(){return this.pointImage};mxConstraintHandler.prototype.isEventIgnored=function(){return false};
-mxConstraintHandler.prototype.update=function(a,b){if(this.isEnabled()&&!this.isEventIgnored(a)){var c=this.getTolerance(),d=new mxRectangle(a.getGraphX()-c,a.getGraphY()-c,2*c,2*c),e=a.getCell()!=null?this.graph.isCellConnectable(a.getCell()):false;if(this.currentFocusArea==null||!mxUtils.intersects(this.currentFocusArea,d)||a.getState()!=null&&this.currentFocus!=null&&e){this.currentFocusArea=null;if(a.getState()!=this.currentFocus){this.currentFocus=null;this.constraints=a.getState()!=null&&e?
-this.graph.getAllConnectionConstraints(a.getState(),b):null;if(this.constraints!=null){this.currentFocus=a.getState();this.currentFocusArea=new mxRectangle(a.getState().x,a.getState().y,a.getState().width,a.getState().height);if(this.focusIcons!=null){for(e=0;e<this.focusIcons.length;e++)this.focusIcons[e].destroy();this.focusPoints=this.focusIcons=null}this.focusIcons=[];this.focusPoints=[];for(e=0;e<this.constraints.length;e++){var f=this.graph.getConnectionPoint(a.getState(),this.constraints[e]),
-g=this.getImageForConstraint(a.getState(),this.constraints[e],f),h=g.src,g=new mxRectangle(f.x-g.width/2,f.y-g.height/2,g.width,g.height),g=new mxImageShape(g,h);g.dialect=this.graph.dialect==mxConstants.DIALECT_SVG?mxConstants.DIALECT_SVG:mxConstants.DIALECT_VML;g.init(this.graph.getView().getOverlayPane());g.node.previousSibling!=null&&g.node.parentNode.insertBefore(g.node,g.node.parentNode.firstChild);h=mxUtils.bind(this,function(){return this.currentFocus!=null?this.currentFocus:a.getState()});
-g.redraw();mxEvent.redirectMouseEvents(g.node,this.graph,h);this.currentFocusArea.add(g.bounds);this.focusIcons.push(g);this.focusPoints.push(f)}this.currentFocusArea.grow(c)}else if(this.focusIcons!=null){if(this.focusHighlight!=null){this.focusHighlight.destroy();this.focusHighlight=null}for(e=0;e<this.focusIcons.length;e++)this.focusIcons[e].destroy();this.focusPoints=this.focusIcons=null}}}this.currentPoint=this.currentConstraint=null;if(this.focusIcons!=null&&this.constraints!=null&&(a.getState()==
-null||this.currentFocus==a.getState()))for(e=0;e<this.focusIcons.length;e++)if(mxUtils.intersects(this.focusIcons[e].bounds,d)){this.currentConstraint=this.constraints[e];this.currentPoint=this.focusPoints[e];c=this.focusIcons[e].bounds.clone();c.grow(mxClient.IS_IE?3:2);if(mxClient.IS_IE){c.width=c.width-1;c.height=c.height-1}if(this.focusHighlight==null){c=new mxRectangleShape(c,null,this.highlightColor,3);c.dialect=this.graph.dialect==mxConstants.DIALECT_SVG?mxConstants.DIALECT_SVG:mxConstants.DIALECT_VML;
-c.init(this.graph.getView().getOverlayPane());this.focusHighlight=c;h=mxUtils.bind(this,function(){return this.currentFocus!=null?this.currentFocus:a.getState()});mxEvent.redirectMouseEvents(c.node,this.graph,h)}else{this.focusHighlight.bounds=c;this.focusHighlight.redraw()}break}if(this.currentConstraint==null&&this.focusHighlight!=null){this.focusHighlight.destroy();this.focusHighlight=null}}};mxConstraintHandler.prototype.destroy=function(){this.reset()};
-function mxRubberband(a){if(a!=null){this.graph=a;this.graph.addMouseListener(this);this.panHandler=mxUtils.bind(this,function(){this.repaint()});this.graph.addListener(mxEvent.PAN,this.panHandler);mxClient.IS_IE&&mxEvent.addListener(window,"unload",mxUtils.bind(this,function(){this.destroy()}))}}mxRubberband.prototype.defaultOpacity=20;mxRubberband.prototype.enabled=!0;mxRubberband.prototype.div=null;mxRubberband.prototype.sharedDiv=null;mxRubberband.prototype.currentX=0;
-mxRubberband.prototype.currentY=0;mxRubberband.prototype.isEnabled=function(){return this.enabled};mxRubberband.prototype.setEnabled=function(a){this.enabled=a};
-mxRubberband.prototype.mouseDown=function(a,b){if(!b.isConsumed()&&this.isEnabled()&&this.graph.isEnabled()&&(this.graph.isForceMarqueeEvent(b.getEvent())||b.getState()==null)){var c=mxUtils.getOffset(this.graph.container),d=mxUtils.getScrollOrigin(this.graph.container);d.x=d.x-c.x;d.y=d.y-c.y;this.start(b.getX()+d.x,b.getY()+d.y);if(mxClient.IS_NS&&!mxClient.IS_SF&&!mxClient.IS_GC){var e=this.graph.container,f=function(a){var a=new mxMouseEvent(a),b=mxUtils.convertPoint(e,a.getX(),a.getY());a.graphX=
-b.x;a.graphY=b.y;return a};this.dragHandler=mxUtils.bind(this,function(a){this.mouseMove(this.graph,f(a))});this.dropHandler=mxUtils.bind(this,function(a){this.mouseUp(this.graph,f(a))});mxEvent.addListener(document,"mousemove",this.dragHandler);mxEvent.addListener(document,"mouseup",this.dropHandler)}b.consume(false)}};mxRubberband.prototype.start=function(a,b){this.first=new mxPoint(a,b)};
-mxRubberband.prototype.mouseMove=function(a,b){if(!b.isConsumed()&&this.first!=null){var c=mxUtils.getScrollOrigin(this.graph.container),d=mxUtils.getOffset(this.graph.container);c.x=c.x-d.x;c.y=c.y-d.y;var d=b.getX()+c.x,c=b.getY()+c.y,e=this.first.x-d,f=this.first.y-c,g=this.graph.tolerance;if(this.div!=null||Math.abs(e)>g||Math.abs(f)>g){if(this.div==null)this.div=this.createShape();mxUtils.clearSelection();this.update(d,c);b.consume()}}};
-mxRubberband.prototype.createShape=function(){if(this.sharedDiv==null){this.sharedDiv=document.createElement("div");this.sharedDiv.className="mxRubberband";mxUtils.setOpacity(this.sharedDiv,this.defaultOpacity)}this.graph.container.appendChild(this.sharedDiv);return this.sharedDiv};mxRubberband.prototype.mouseUp=function(a,b){var c=this.div!=null;this.reset();if(c){this.graph.selectRegion(new mxRectangle(this.x,this.y,this.width,this.height),b.getEvent());b.consume()}};
-mxRubberband.prototype.reset=function(){this.div!=null&&this.div.parentNode.removeChild(this.div);if(this.dragHandler!=null){mxEvent.removeListener(document,"mousemove",this.dragHandler);this.dragHandler=null}if(this.dropHandler!=null){mxEvent.removeListener(document,"mouseup",this.dropHandler);this.dropHandler=null}this.currentY=this.currentX=0;this.div=this.first=null};mxRubberband.prototype.update=function(a,b){this.currentX=a;this.currentY=b;this.repaint()};
-mxRubberband.prototype.repaint=function(){if(this.div!=null){var a=this.currentX-this.graph.panDx,b=this.currentY-this.graph.panDy;this.x=Math.min(this.first.x,a);this.y=Math.min(this.first.y,b);this.width=Math.max(this.first.x,a)-this.x;this.height=Math.max(this.first.y,b)-this.y;a=mxClient.IS_VML?this.graph.panDy:0;this.div.style.left=this.x+(mxClient.IS_VML?this.graph.panDx:0)+"px";this.div.style.top=this.y+a+"px";this.div.style.width=Math.max(1,this.width)+"px";this.div.style.height=Math.max(1,
-this.height)+"px"}};mxRubberband.prototype.destroy=function(){if(!this.destroyed){this.destroyed=true;this.graph.removeMouseListener(this);this.graph.removeListener(this.panHandler);this.reset();if(this.sharedDiv!=null)this.sharedDiv=null}};function mxVertexHandler(a){if(a!=null){this.state=a;this.init()}}mxVertexHandler.prototype.graph=null;mxVertexHandler.prototype.state=null;mxVertexHandler.prototype.singleSizer=!1;mxVertexHandler.prototype.index=null;
-mxVertexHandler.prototype.allowHandleBoundsCheck=!0;mxVertexHandler.prototype.crisp=!0;mxVertexHandler.prototype.handleImage=null;mxVertexHandler.prototype.tolerance=0;
-mxVertexHandler.prototype.init=function(){this.graph=this.state.view.graph;this.selectionBounds=this.getSelectionBounds(this.state);this.bounds=new mxRectangle(this.selectionBounds.x,this.selectionBounds.y,this.selectionBounds.width,this.selectionBounds.height);this.selectionBorder=this.createSelectionShape(this.bounds);this.selectionBorder.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;this.selectionBorder.init(this.graph.getView().getOverlayPane());
-this.selectionBorder.dialect==mxConstants.DIALECT_SVG?this.selectionBorder.node.setAttribute("pointer-events","none"):this.selectionBorder.node.style.background="";if(this.graph.isCellMovable(this.state.cell))this.selectionBorder.node.style.cursor=mxConstants.CURSOR_MOVABLE_VERTEX;mxEvent.redirectMouseEvents(this.selectionBorder.node,this.graph,this.state);if(mxGraphHandler.prototype.maxCells<=0||this.graph.getSelectionCount()<mxGraphHandler.prototype.maxCells){var a=this.graph.isCellResizable(this.state.cell);
-this.sizers=[];if(a||this.graph.isLabelMovable(this.state.cell)&&this.state.width>=2&&this.state.height>=2){var b=0;if(a){if(!this.singleSizer){this.sizers.push(this.createSizer("nw-resize",b++));this.sizers.push(this.createSizer("n-resize",b++));this.sizers.push(this.createSizer("ne-resize",b++));this.sizers.push(this.createSizer("w-resize",b++));this.sizers.push(this.createSizer("e-resize",b++));this.sizers.push(this.createSizer("sw-resize",b++));this.sizers.push(this.createSizer("s-resize",b++))}this.sizers.push(this.createSizer("se-resize",
-b++))}a=this.graph.model.getGeometry(this.state.cell);if(a!=null&&!a.relative&&!this.graph.isSwimlane(this.state.cell)&&this.graph.isLabelMovable(this.state.cell)){this.labelShape=this.createSizer(mxConstants.CURSOR_LABEL_HANDLE,mxEvent.LABEL_HANDLE,mxConstants.LABEL_HANDLE_SIZE,mxConstants.LABEL_HANDLE_FILLCOLOR);this.sizers.push(this.labelShape)}}else if(this.graph.isCellMovable(this.state.cell)&&!this.graph.isCellResizable(this.state.cell)&&this.state.width<2&&this.state.height<2){this.labelShape=
-this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX,null,null,mxConstants.LABEL_HANDLE_FILLCOLOR);this.sizers.push(this.labelShape)}}this.redraw()};mxVertexHandler.prototype.getSelectionBounds=function(a){return new mxRectangle(a.x,a.y,a.width,a.height)};mxVertexHandler.prototype.createSelectionShape=function(a){a=new mxRectangleShape(a,null,this.getSelectionColor());a.strokewidth=this.getSelectionStrokeWidth();a.isDashed=this.isSelectionDashed();a.crisp=this.crisp;return a};
-mxVertexHandler.prototype.getSelectionColor=function(){return mxConstants.VERTEX_SELECTION_COLOR};mxVertexHandler.prototype.getSelectionStrokeWidth=function(){return mxConstants.VERTEX_SELECTION_STROKEWIDTH};mxVertexHandler.prototype.isSelectionDashed=function(){return mxConstants.VERTEX_SELECTION_DASHED};
-mxVertexHandler.prototype.createSizer=function(a,b,c,d){c=c||mxConstants.HANDLE_SIZE;c=this.createSizerShape(new mxRectangle(0,0,c,c),b,d);if(this.state.text!=null&&this.state.text.node.parentNode==this.graph.container){c.bounds.height=c.bounds.height-1;c.bounds.width=c.bounds.width-1;c.dialect=mxConstants.DIALECT_STRICTHTML;c.init(this.graph.container)}else{c.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;c.init(this.graph.getView().getOverlayPane())}mxEvent.redirectMouseEvents(c.node,
-this.graph,this.state);if(this.graph.isEnabled())c.node.style.cursor=a;if(!this.isSizerVisible(b))c.node.style.visibility="hidden";return c};mxVertexHandler.prototype.isSizerVisible=function(){return true};
-mxVertexHandler.prototype.createSizerShape=function(a,b,c){if(this.handleImage!=null){a.width=this.handleImage.width;a.height=this.handleImage.height;return new mxImageShape(a,this.handleImage.src)}a=new mxRectangleShape(a,c||mxConstants.HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR);a.crisp=this.crisp;return a};mxVertexHandler.prototype.moveSizerTo=function(a,b,c){if(a!=null){a.bounds.x=b-a.bounds.width/2;a.bounds.y=c-a.bounds.height/2;a.redraw()}};
-mxVertexHandler.prototype.getHandleForEvent=function(a){if(a.isSource(this.labelShape))return mxEvent.LABEL_HANDLE;if(this.sizers!=null)for(var b=this.tolerance,b=this.allowHandleBoundsCheck&&(mxClient.IS_IE||b>0)?new mxRectangle(a.getGraphX()-b,a.getGraphY()-b,2*b,2*b):null,c=0;c<this.sizers.length;c++)if(a.isSource(this.sizers[c])||b!=null&&this.sizers[c].node.style.visibility!="hidden"&&mxUtils.intersects(this.sizers[c].bounds,b))return c;return null};
-mxVertexHandler.prototype.mouseDown=function(a,b){if(!b.isConsumed()&&this.graph.isEnabled()&&!this.graph.isForceMarqueeEvent(b.getEvent())&&(this.tolerance>0||b.getState()==this.state)){var c=this.getHandleForEvent(b);if(c!=null){this.start(b.getX(),b.getY(),c);b.consume()}}};
-mxVertexHandler.prototype.start=function(a,b,c){a=mxUtils.convertPoint(this.graph.container,a,b);this.startX=a.x;this.startY=a.y;this.index=c;this.selectionBorder.node.style.visibility="hidden";this.preview=this.createSelectionShape(this.bounds);if(this.state.text!=null&&this.state.text.node.parentNode==this.graph.container){this.preview.dialect=mxConstants.DIALECT_STRICTHTML;this.preview.init(this.graph.container)}else{this.preview.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:
-mxConstants.DIALECT_SVG;this.preview.init(this.graph.view.getOverlayPane())}};
-mxVertexHandler.prototype.mouseMove=function(a,b){if(!b.isConsumed()&&this.index!=null){var c=new mxPoint(b.getGraphX(),b.getGraphY()),d=this.graph.isGridEnabledEvent(b.getEvent()),e=this.graph.getView().scale;if(this.index==mxEvent.LABEL_HANDLE){if(d){c.x=this.graph.snap(c.x/e)*e;c.y=this.graph.snap(c.y/e)*e}this.moveSizerTo(this.sizers[this.sizers.length-1],c.x,c.y);b.consume()}else if(this.index!=null){this.bounds=this.union(this.selectionBounds,c.x-this.startX,c.y-this.startY,this.index,d,e,this.graph.view.translate);
-this.drawPreview();b.consume()}}else this.getHandleForEvent(b)!=null&&b.consume(false)};mxVertexHandler.prototype.mouseUp=function(a,b){if(!b.isConsumed()&&this.index!=null&&this.state!=null){var c=new mxPoint(b.getGraphX(),b.getGraphY()),d=this.graph.getView().scale,e=this.graph.isGridEnabledEvent(b.getEvent());this.resizeCell(this.state.cell,(c.x-this.startX)/d,(c.y-this.startY)/d,this.index,e);this.reset();b.consume()}};
-mxVertexHandler.prototype.reset=function(){this.index=null;if(this.preview!=null){this.preview.destroy();this.preview=null}if(this.selectionBorder!=null){this.selectionBounds=this.getSelectionBounds(this.state);this.selectionBorder.node.style.visibility="visible";this.bounds=new mxRectangle(this.selectionBounds.x,this.selectionBounds.y,this.selectionBounds.width,this.selectionBounds.height);this.drawPreview()}};
-mxVertexHandler.prototype.resizeCell=function(a,b,c,d,e){var f=this.graph.model.getGeometry(a);if(d==mxEvent.LABEL_HANDLE){c=this.graph.view.scale;b=(this.labelShape.bounds.getCenterX()-this.startX)/c;c=(this.labelShape.bounds.getCenterY()-this.startY)/c;f=f.clone();if(f.offset==null)f.offset=new mxPoint(b,c);else{f.offset.x=f.offset.x+b;f.offset.y=f.offset.y+c}this.graph.model.setGeometry(a,f)}else{b=this.union(f,b,c,d,e,1,new mxPoint(0,0));this.graph.resizeCell(a,b)}};
-mxVertexHandler.prototype.union=function(a,b,c,d,e,f,g){if(this.singleSizer){var g=a.x+a.width+b,h=a.y+a.height+c;if(e){g=this.graph.snap(g/f)*f;h=this.graph.snap(h/f)*f}f=new mxRectangle(a.x,a.y,0,0);f.add(new mxRectangle(g,h,0,0));return f}var h=a.x-g.x*f,k=h+a.width,i=a.y-g.y*f,a=i+a.height;if(d>4){a=a+c;e&&(a=this.graph.snap(a/f)*f)}else if(d<3){i=i+c;e&&(i=this.graph.snap(i/f)*f)}if(d==0||d==3||d==5){h=h+b;e&&(h=this.graph.snap(h/f)*f)}else if(d==2||d==4||d==7){k=k+b;e&&(k=this.graph.snap(k/
-f)*f)}e=k-h;a=a-i;if(e<0){h=h+e;e=Math.abs(e)}if(a<0){i=i+a;a=Math.abs(a)}return new mxRectangle(h+g.x*f,i+g.y*f,e,a)};
-mxVertexHandler.prototype.redraw=function(){this.selectionBounds=this.getSelectionBounds(this.state);this.bounds=new mxRectangle(this.selectionBounds.x,this.selectionBounds.y,this.selectionBounds.width,this.selectionBounds.height);if(this.sizers!=null){var a=this.state,b=a.x+a.width,c=a.y+a.height;if(this.singleSizer)this.moveSizerTo(this.sizers[0],b,c);else{var d=a.x+a.width/2,e=a.y+a.height/2;if(this.sizers.length>1){this.moveSizerTo(this.sizers[0],a.x,a.y);this.moveSizerTo(this.sizers[1],d,a.y);
-this.moveSizerTo(this.sizers[2],b,a.y);this.moveSizerTo(this.sizers[3],a.x,e);this.moveSizerTo(this.sizers[4],b,e);this.moveSizerTo(this.sizers[5],a.x,c);this.moveSizerTo(this.sizers[6],d,c);this.moveSizerTo(this.sizers[7],b,c);this.moveSizerTo(this.sizers[8],d+a.absoluteOffset.x,e+a.absoluteOffset.y)}else this.state.width>=2&&this.state.height>=2?this.moveSizerTo(this.sizers[0],d+a.absoluteOffset.x,e+a.absoluteOffset.y):this.moveSizerTo(this.sizers[0],a.x,a.y)}}this.drawPreview()};
-mxVertexHandler.prototype.drawPreview=function(){if(this.preview!=null){this.preview.bounds=this.bounds;if(this.preview.node.parentNode==this.graph.container){this.preview.bounds.width=Math.max(0,this.preview.bounds.width-1);this.preview.bounds.height=Math.max(0,this.preview.bounds.height-1)}this.preview.redraw()}this.selectionBorder.bounds=this.bounds;this.selectionBorder.redraw()};
-mxVertexHandler.prototype.destroy=function(){if(this.preview!=null){this.preview.destroy();this.preview=null}this.selectionBorder.destroy();this.labelShape=this.selectionBorder=null;if(this.sizers!=null)for(var a=0;a<this.sizers.length;a++){this.sizers[a].destroy();this.sizers[a]=null}};function mxEdgeHandler(a){if(a!=null){this.state=a;this.init()}}mxEdgeHandler.prototype.graph=null;mxEdgeHandler.prototype.state=null;mxEdgeHandler.prototype.marker=null;mxEdgeHandler.prototype.constraintHandler=null;
-mxEdgeHandler.prototype.error=null;mxEdgeHandler.prototype.shape=null;mxEdgeHandler.prototype.bends=null;mxEdgeHandler.prototype.labelShape=null;mxEdgeHandler.prototype.cloneEnabled=!0;mxEdgeHandler.prototype.addEnabled=!1;mxEdgeHandler.prototype.removeEnabled=!1;mxEdgeHandler.prototype.preferHtml=!1;mxEdgeHandler.prototype.allowHandleBoundsCheck=!0;mxEdgeHandler.prototype.snapToTerminals=!1;mxEdgeHandler.prototype.crisp=!0;mxEdgeHandler.prototype.handleImage=null;
-mxEdgeHandler.prototype.tolerance=0;
-mxEdgeHandler.prototype.init=function(){this.graph=this.state.view.graph;this.marker=this.createMarker();this.constraintHandler=new mxConstraintHandler(this.graph);this.points=[];this.abspoints=this.getSelectionPoints(this.state);this.shape=this.createSelectionShape(this.abspoints);this.shape.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;this.shape.init(this.graph.getView().getOverlayPane());this.shape.node.style.cursor=mxConstants.CURSOR_MOVABLE_EDGE;
-var a=mxClient.IS_TOUCH?"touchstart":"mousedown",b=mxClient.IS_TOUCH?"touchmove":"mousemove",c=mxClient.IS_TOUCH?"touchend":"mouseup";mxEvent.addListener(this.shape.node,"dblclick",mxUtils.bind(this,function(a){this.graph.dblClick(a,this.state.cell)}));mxEvent.addListener(this.shape.node,a,mxUtils.bind(this,function(a){this.addEnabled&&this.isAddPointEvent(a)?this.addPoint(this.state,a):this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(a,this.state))}));mxEvent.addListener(this.shape.node,
-b,mxUtils.bind(this,function(a){var b=this.state.cell;if(this.index!=null){var c=mxUtils.convertPoint(this.graph.container,mxEvent.getClientX(a),mxEvent.getClientY(a)),b=this.graph.getCellAt(c.x,c.y);this.graph.isSwimlane(b)&&this.graph.hitsSwimlaneContent(b,c.x,c.y)&&(b=null)}this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(a,this.graph.getView().getState(b)))}));mxEvent.addListener(this.shape.node,c,mxUtils.bind(this,function(a){this.graph.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(a,
-this.state))}));this.preferHtml=this.state.text!=null&&this.state.text.node.parentNode==this.graph.container;if(!this.preferHtml){a=this.state.getVisibleTerminalState(true);if(a!=null)this.preferHtml=a.text!=null&&a.text.node.parentNode==this.graph.container;if(!this.preferHtml){a=this.state.getVisibleTerminalState(false);if(a!=null)this.preferHtml=a.text!=null&&a.text.node.parentNode==this.graph.container}}if(this.graph.getSelectionCount()<mxGraphHandler.prototype.maxCells||mxGraphHandler.prototype.maxCells<=
-0)this.bends=this.createBends();this.label=new mxPoint(this.state.absoluteOffset.x,this.state.absoluteOffset.y);this.labelShape=new mxRectangleShape(new mxRectangle,mxConstants.LABEL_HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR);this.initBend(this.labelShape);this.labelShape.node.style.cursor=mxConstants.CURSOR_LABEL_HANDLE;mxEvent.redirectMouseEvents(this.labelShape.node,this.graph,this.state);this.redraw()};mxEdgeHandler.prototype.isAddPointEvent=function(a){return mxEvent.isShiftDown(a)};
-mxEdgeHandler.prototype.isRemovePointEvent=function(a){return mxEvent.isShiftDown(a)};mxEdgeHandler.prototype.getSelectionPoints=function(a){return a.absolutePoints};mxEdgeHandler.prototype.createSelectionShape=function(a){a=new mxPolyline(a,this.getSelectionColor());a.strokewidth=this.getSelectionStrokeWidth();a.isDashed=this.isSelectionDashed();return a};mxEdgeHandler.prototype.getSelectionColor=function(){return mxConstants.EDGE_SELECTION_COLOR};
-mxEdgeHandler.prototype.getSelectionStrokeWidth=function(){return mxConstants.EDGE_SELECTION_STROKEWIDTH};mxEdgeHandler.prototype.isSelectionDashed=function(){return mxConstants.EDGE_SELECTION_DASHED};mxEdgeHandler.prototype.isConnectableCell=function(){return true};
-mxEdgeHandler.prototype.createMarker=function(){var a=new mxCellMarker(this.graph),b=this;a.getCell=function(a){var d=mxCellMarker.prototype.getCell.apply(this,arguments);if(!b.isConnectableCell(d))return null;var e=b.graph.getModel();if(d==b.state.cell||d!=null&&!b.graph.connectableEdges&&e.isEdge(d))d=null;return d};a.isValidState=function(a){var d=b.graph.getModel(),d=b.graph.view.getTerminalPort(a,b.graph.view.getState(d.getTerminal(b.state.cell,!b.isSource)),!b.isSource),d=d!=null?d.cell:null;
-b.error=b.validateConnection(b.isSource?a.cell:d,b.isSource?d:a.cell);return b.error==null};return a};mxEdgeHandler.prototype.validateConnection=function(a,b){return this.graph.getEdgeValidationError(this.state.cell,a,b)};
-mxEdgeHandler.prototype.createBends=function(){for(var a=this.state.cell,b=[],c=0;c<this.abspoints.length;c++)if(this.isHandleVisible(c)){var d=c==this.abspoints.length-1;if((d=c==0||d)||this.graph.isCellBendable(a)){var e=this.createHandleShape(c);this.initBend(e);mxClient.IS_TOUCH&&e.node.setAttribute("pointer-events","none");if(this.isHandleEnabled(c))if(mxClient.IS_TOUCH){var f=mxUtils.bind(this,function(a){a=mxUtils.convertPoint(this.graph.container,mxEvent.getClientX(a),mxEvent.getClientY(a));
-return this.graph.view.getState(this.graph.getCellAt(a.x,a.y))});mxEvent.redirectMouseEvents(e.node,this.graph,f)}else{e.node.style.cursor=mxConstants.CURSOR_BEND_HANDLE;mxEvent.redirectMouseEvents(e.node,this.graph,this.state)}b.push(e);if(!d){this.points.push(new mxPoint(0,0));e.node.style.visibility="hidden"}}}return b};mxEdgeHandler.prototype.isHandleEnabled=function(){return true};mxEdgeHandler.prototype.isHandleVisible=function(){return true};
-mxEdgeHandler.prototype.createHandleShape=function(){if(this.handleImage!=null)return new mxImageShape(new mxRectangle(0,0,this.handleImage.width,this.handleImage.height),this.handleImage.src);var a=mxConstants.HANDLE_SIZE;this.preferHtml&&(a=a-1);return new mxRectangleShape(new mxRectangle(0,0,a,a),mxConstants.HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR)};
-mxEdgeHandler.prototype.initBend=function(a){a.crisp=this.crisp;if(this.preferHtml){a.dialect=mxConstants.DIALECT_STRICTHTML;a.init(this.graph.container)}else{a.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;a.init(this.graph.getView().getOverlayPane())}};
-mxEdgeHandler.prototype.getHandleForEvent=function(a){if(this.bends!=null)for(var b=this.tolerance,b=this.allowHandleBoundsCheck&&(mxClient.IS_IE||b>0)?new mxRectangle(a.getGraphX()-b,a.getGraphY()-b,2*b,2*b):null,c=0;c<this.bends.length;c++)if(a.isSource(this.bends[c])||b!=null&&this.bends[c].node.style.visibility!="hidden"&&mxUtils.intersects(this.bends[c].bounds,b))return c;if(a.isSource(this.labelShape)||a.isSource(this.state.text))if(!mxClient.IS_SF&&!mxClient.IS_GC||a.getSource().nodeName!=
-"SELECT")return mxEvent.LABEL_HANDLE;return null};mxEdgeHandler.prototype.mouseDown=function(a,b){var c=null,c=this.getHandleForEvent(b);if(c!=null&&!b.isConsumed()&&this.graph.isEnabled()&&!this.graph.isForceMarqueeEvent(b.getEvent())){this.removeEnabled&&this.isRemovePointEvent(b.getEvent())?this.removePoint(this.state,c):(c!=mxEvent.LABEL_HANDLE||this.graph.isLabelMovable(b.getCell()))&&this.start(b.getX(),b.getY(),c);b.consume()}};
-mxEdgeHandler.prototype.start=function(a,b,c){this.startX=a;this.startY=b;this.isSource=this.bends==null?false:c==0;this.isTarget=this.bends==null?false:c==this.bends.length-1;this.isLabel=c==mxEvent.LABEL_HANDLE;if(this.isSource||this.isTarget){a=this.state.cell;b=this.graph.model.getTerminal(a,this.isSource);if(b==null&&this.graph.isTerminalPointMovable(a,this.isSource)||b!=null&&this.graph.isCellDisconnectable(a,b,this.isSource))this.index=c}else this.index=c};
-mxEdgeHandler.prototype.clonePreviewState=function(){return this.state.clone()};mxEdgeHandler.prototype.getSnapToTerminalTolerance=function(){return this.graph.gridSize*this.graph.view.scale/2};
-mxEdgeHandler.prototype.getPointForEvent=function(a){var b=new mxPoint(a.getGraphX(),a.getGraphY()),c=this.getSnapToTerminalTolerance(),d=this.graph.getView(),e=false,f=false;if(this.snapToTerminals&&c>0){var g=function(a){if(a!=null){var d=a.x;if(Math.abs(b.x-d)<c){b.x=d;e=true}a=a.y;if(Math.abs(b.y-a)<c){b.y=a;f=true}}},h=function(a){a!=null&&g.call(this,new mxPoint(d.getRoutingCenterX(a),d.getRoutingCenterY(a)))};h.call(this,this.state.getVisibleTerminalState(true));h.call(this,this.state.getVisibleTerminalState(false));
-if(this.abspoints!=null)for(h=0;h<this.abspoints;h++)h!=this.index&&g.call(this,this.abspoints[h])}if(this.graph.isGridEnabledEvent(a.getEvent())){a=d.scale;h=d.translate;if(!e)b.x=(this.graph.snap(b.x/a-h.x)+h.x)*a;if(!f)b.y=(this.graph.snap(b.y/a-h.y)+h.y)*a}return b};
-mxEdgeHandler.prototype.getPreviewTerminalState=function(a){this.constraintHandler.update(a,this.isSource);this.marker.process(a);var a=this.marker.getValidState(),b=null;this.constraintHandler.currentFocus!=null&&this.constraintHandler.currentConstraint!=null&&this.marker.reset();if(a!=null)b=a;else if(this.constraintHandler.currentConstraint!=null&&this.constraintHandler.currentFocus!=null)b=this.constraintHandler.currentFocus;return b};
-mxEdgeHandler.prototype.getPreviewPoints=function(a){var b=this.graph.getCellGeometry(this.state.cell),b=b.points!=null?b.points.slice():null;if(!this.isSource&&!this.isTarget){this.convertPoint(a,false);b==null?b=[a]:b[this.index-1]=a}else this.graph.resetEdgesOnConnect&&(b=null);return b};
-mxEdgeHandler.prototype.updatePreviewState=function(a,b,c){var d=this.isSource?c:this.state.getVisibleTerminalState(true),e=this.isTarget?c:this.state.getVisibleTerminalState(false),f=this.graph.getConnectionConstraint(a,d,true),g=this.graph.getConnectionConstraint(a,e,false),h=this.constraintHandler.currentConstraint;h==null&&(h=new mxConnectionConstraint);this.isSource?f=h:this.isTarget&&(g=h);(!this.isSource||d!=null)&&a.view.updateFixedTerminalPoint(a,d,true,f);(!this.isTarget||e!=null)&&a.view.updateFixedTerminalPoint(a,
-e,false,g);if((this.isSource||this.isTarget)&&c==null){a.setAbsoluteTerminalPoint(b,this.isSource);if(this.marker.getMarkedState()==null)this.error=this.graph.allowDanglingEdges?null:""}a.view.updatePoints(a,this.points,d,e);a.view.updateFloatingTerminalPoints(a,d,e)};
-mxEdgeHandler.prototype.mouseMove=function(a,b){if(this.index!=null&&this.marker!=null){var c=this.getPointForEvent(b);if(this.isLabel){this.label.x=c.x;this.label.y=c.y}else{this.points=this.getPreviewPoints(c);var d=this.isSource||this.isTarget?this.getPreviewTerminalState(b):null,e=this.clonePreviewState(c,d!=null?d.cell:null);this.updatePreviewState(e,c,d);this.setPreviewColor(this.error==null?this.marker.validColor:this.marker.invalidColor);this.abspoints=e.absolutePoints;this.active=true}this.drawPreview();
-mxEvent.consume(b.getEvent());b.consume()}else mxClient.IS_IE&&this.getHandleForEvent(b)!=null&&b.consume(false)};
-mxEdgeHandler.prototype.mouseUp=function(a,b){if(this.index!=null&&this.marker!=null){var c=this.state.cell;if(b.getX()!=this.startX||b.getY()!=this.startY)if(this.error!=null)this.error.length>0&&this.graph.validationAlert(this.error);else if(this.isLabel)this.moveLabel(this.state,this.label.x,this.label.y);else if(this.isSource||this.isTarget){var d=null;if(this.constraintHandler.currentConstraint!=null&&this.constraintHandler.currentFocus!=null)d=this.constraintHandler.currentFocus.cell;if(d==
-null&&this.marker.hasValidState())d=this.marker.validState.cell;if(d!=null)c=this.connect(c,d,this.isSource,this.graph.isCloneEvent(b.getEvent())&&this.cloneEnabled&&this.graph.isCellsCloneable(),b);else if(this.graph.isAllowDanglingEdges()){d=this.abspoints[this.isSource?0:this.abspoints.length-1];d.x=d.x/this.graph.view.scale-this.graph.view.translate.x;d.y=d.y/this.graph.view.scale-this.graph.view.translate.y;var e=this.graph.getView().getState(this.graph.getModel().getParent(c));if(e!=null){d.x=
-d.x-e.origin.x;d.y=d.y-e.origin.y}d.x=d.x-this.graph.panDx/this.graph.view.scale;d.y=d.y-this.graph.panDy/this.graph.view.scale;this.changeTerminalPoint(c,d,this.isSource)}}else if(this.active)this.changePoints(c,this.points);else{this.graph.getView().invalidate(this.state.cell);this.graph.getView().revalidate(this.state.cell)}if(this.marker!=null){this.reset();c!=this.state.cell&&this.graph.setSelectionCell(c)}b.consume()}};
-mxEdgeHandler.prototype.reset=function(){this.points=this.label=this.index=this.error=null;this.isTarget=this.isSource=this.isLabel=this.active=false;this.marker.reset();this.constraintHandler.reset();this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);this.redraw()};mxEdgeHandler.prototype.setPreviewColor=function(a){if(this.shape!=null&&this.shape.node!=null)this.shape.dialect==mxConstants.DIALECT_SVG?this.shape.innerNode.setAttribute("stroke",a):this.shape.node.strokecolor=a};
-mxEdgeHandler.prototype.convertPoint=function(a,b){var c=this.graph.getView().getScale(),d=this.graph.getView().getTranslate();if(b){a.x=this.graph.snap(a.x);a.y=this.graph.snap(a.y)}a.x=Math.round(a.x/c-d.x);a.y=Math.round(a.y/c-d.y);c=this.graph.getView().getState(this.graph.getModel().getParent(this.state.cell));if(c!=null){a.x=a.x-c.origin.x;a.y=a.y-c.origin.y}return a};
-mxEdgeHandler.prototype.moveLabel=function(a,b,c){var d=this.graph.getModel(),e=d.getGeometry(a.cell);if(e!=null){var e=e.clone(),f=this.graph.getView().getRelativePoint(a,b,c);e.x=f.x;e.y=f.y;var g=this.graph.getView().scale;e.offset=new mxPoint(0,0);f=this.graph.view.getPoint(a,e);e.offset=new mxPoint((b-f.x)/g,(c-f.y)/g);d.setGeometry(a.cell,e)}};
-mxEdgeHandler.prototype.connect=function(a,b,c,d){var e=this.graph.getModel(),f=e.getParent(a);e.beginUpdate();try{if(d){var g=a.clone();e.add(f,g,e.getChildCount(f));var h=e.getTerminal(a,!c);this.graph.connectCell(g,h,!c);a=g}var k=this.constraintHandler.currentConstraint;k==null&&(k=new mxConnectionConstraint);this.graph.connectCell(a,b,c,k)}finally{e.endUpdate()}return a};
-mxEdgeHandler.prototype.changeTerminalPoint=function(a,b,c){var d=this.graph.getModel(),e=d.getGeometry(a);if(e!=null){d.beginUpdate();try{e=e.clone();e.setTerminalPoint(b,c);d.setGeometry(a,e);this.graph.connectCell(a,null,c,new mxConnectionConstraint)}finally{d.endUpdate()}}};mxEdgeHandler.prototype.changePoints=function(a,b){var c=this.graph.getModel(),d=c.getGeometry(a);if(d!=null){d=d.clone();d.points=b;c.setGeometry(a,d)}};
-mxEdgeHandler.prototype.addPoint=function(a,b){var c=this.graph.getCellGeometry(a.cell);if(c!=null){var c=c.clone(),d=mxUtils.convertPoint(this.graph.container,mxEvent.getClientX(b),mxEvent.getClientY(b)),e=mxUtils.findNearestSegment(a,d.x,d.y),f=this.graph.isGridEnabledEvent(b);this.convertPoint(d,f);c.points==null?c.points=[d]:c.points.splice(e,0,d);this.graph.getModel().setGeometry(a.cell,c);this.destroy();this.init();mxEvent.consume(b)}};
-mxEdgeHandler.prototype.removePoint=function(a,b){if(b>0&&b<this.abspoints.length-1){var c=this.graph.getCellGeometry(this.state.cell);if(c!=null&&c.points!=null){c=c.clone();c.points.splice(b-1,1);this.graph.getModel().setGeometry(a.cell,c);this.destroy();this.init()}}};
-mxEdgeHandler.prototype.getHandleFillColor=function(a){var a=a==0,b=this.state.cell,c=this.graph.getModel().getTerminal(b,a),d=mxConstants.HANDLE_FILLCOLOR;if(c!=null&&!this.graph.isCellDisconnectable(b,c,a)||c==null&&!this.graph.isTerminalPointMovable(b,a))d=mxConstants.LOCKED_HANDLE_FILLCOLOR;else if(c!=null&&this.graph.isCellDisconnectable(b,c,a))d=mxConstants.CONNECT_HANDLE_FILLCOLOR;return d};
-mxEdgeHandler.prototype.redraw=function(){this.abspoints=this.state.absolutePoints.slice();var a=this.state.cell,b=mxConstants.LABEL_HANDLE_SIZE;this.label=new mxPoint(this.state.absoluteOffset.x,this.state.absoluteOffset.y);this.labelShape.bounds=new mxRectangle(this.label.x-b/2,this.label.y-b/2,b,b);this.labelShape.redraw();b=this.graph.getLabel(a);this.labelShape.node.style.visibility=b!=null&&b.length>0&&this.graph.isLabelMovable(a)?"visible":"hidden";if(this.bends!=null&&this.bends.length>0){var c=
-this.abspoints.length-1,a=this.abspoints[0],b=this.bends[0].bounds;this.bends[0].bounds=new mxRectangle(this.abspoints[0].x-b.width/2,this.abspoints[0].y-b.height/2,b.width,b.height);this.bends[0].fill=this.getHandleFillColor(0);this.bends[0].reconfigure();this.bends[0].redraw();var d=this.abspoints[c],e=this.abspoints[c].x,c=this.abspoints[c].y,f=this.bends.length-1,b=this.bends[f].bounds;this.bends[f].bounds=new mxRectangle(e-b.width/2,c-b.height/2,b.width,b.height);this.bends[f].fill=this.getHandleFillColor(f);
-this.bends[f].reconfigure();this.bends[f].redraw();this.redrawInnerBends(a,d)}this.drawPreview()};
-mxEdgeHandler.prototype.redrawInnerBends=function(){var a=this.graph.getModel().getGeometry(this.state.cell).points;if(a!=null){if(this.points==null)this.points=[];for(var b=1;b<this.bends.length-1;b++)if(this.bends[b]!=null)if(this.abspoints[b]!=null){var c=this.abspoints[b].x,d=this.abspoints[b].y,e=this.bends[b].bounds;this.bends[b].node.style.visibility="visible";this.bends[b].bounds=new mxRectangle(c-e.width/2,d-e.height/2,e.width,e.height);this.bends[b].redraw();this.points[b-1]=a[b-1]}else{this.bends[b].destroy();
-this.bends[b]=null}}};mxEdgeHandler.prototype.drawPreview=function(){if(this.isLabel){var a=mxConstants.LABEL_HANDLE_SIZE;this.labelShape.bounds=new mxRectangle(this.label.x-a/2,this.label.y-a/2,a,a);this.labelShape.redraw()}else{this.shape.points=this.abspoints;this.shape.redraw()}mxUtils.repaintGraph(this.graph,this.shape.points[this.shape.points.length-1])};
-mxEdgeHandler.prototype.destroy=function(){if(this.marker!=null){this.marker.destroy();this.marker=null}if(this.shape!=null){this.shape.destroy();this.shape=null}if(this.labelShape!=null){this.labelShape.destroy();this.labelShape=null}if(this.constraintHandler!=null){this.constraintHandler.destroy();this.constraintHandler=null}if(this.bends!=null)for(var a=0;a<this.bends.length;a++)if(this.bends[a]!=null){this.bends[a].destroy();this.bends[a]=null}};
-function mxElbowEdgeHandler(a){if(a!=null){this.state=a;this.init()}}mxElbowEdgeHandler.prototype=new mxEdgeHandler;mxElbowEdgeHandler.prototype.constructor=mxElbowEdgeHandler;mxElbowEdgeHandler.prototype.flipEnabled=!0;mxElbowEdgeHandler.prototype.doubleClickOrientationResource="none"!=mxClient.language?"doubleClickOrientation":"";
-mxElbowEdgeHandler.prototype.createBends=function(){var a=[],b=this.createHandleShape(0);this.initBend(b);b.node.style.cursor=mxConstants.CURSOR_BEND_HANDLE;mxEvent.redirectMouseEvents(b.node,this.graph,this.state);a.push(b);mxClient.IS_TOUCH&&b.node.setAttribute("pointer-events","none");a.push(this.createVirtualBend());this.points.push(new mxPoint(0,0));b=this.createHandleShape(2);this.initBend(b);b.node.style.cursor=mxConstants.CURSOR_BEND_HANDLE;mxEvent.redirectMouseEvents(b.node,this.graph,this.state);
-a.push(b);mxClient.IS_TOUCH&&b.node.setAttribute("pointer-events","none");return a};
-mxElbowEdgeHandler.prototype.createVirtualBend=function(){var a=this.createHandleShape();this.initBend(a);var b=this.getCursorForBend();a.node.style.cursor=b;b=mxUtils.bind(this,function(a){if(!mxEvent.isConsumed(a)&&this.flipEnabled){this.graph.flipEdge(this.state.cell,a);mxEvent.consume(a)}});mxEvent.redirectMouseEvents(a.node,this.graph,this.state,null,null,null,b);if(!this.graph.isCellBendable(this.state.cell))a.node.style.visibility="hidden";return a};
-mxElbowEdgeHandler.prototype.getCursorForBend=function(){return this.state.style[mxConstants.STYLE_EDGE]==mxEdgeStyle.TopToBottom||this.state.style[mxConstants.STYLE_EDGE]==mxConstants.EDGESTYLE_TOPTOBOTTOM||(this.state.style[mxConstants.STYLE_EDGE]==mxEdgeStyle.ElbowConnector||this.state.style[mxConstants.STYLE_EDGE]==mxConstants.EDGESTYLE_ELBOW)&&this.state.style[mxConstants.STYLE_ELBOW]==mxConstants.ELBOW_VERTICAL?"row-resize":"col-resize"};
-mxElbowEdgeHandler.prototype.getTooltipForNode=function(a){var b=null;if(this.bends!=null&&this.bends[1]!=null&&(a==this.bends[1].node||a.parentNode==this.bends[1].node)){b=this.doubleClickOrientationResource;b=mxResources.get(b)||b}return b};
-mxElbowEdgeHandler.prototype.convertPoint=function(a,b){var c=this.graph.getView().getScale(),d=this.graph.getView().getTranslate(),e=this.state.origin;if(b){a.x=this.graph.snap(a.x);a.y=this.graph.snap(a.y)}a.x=Math.round(a.x/c-d.x-e.x);a.y=Math.round(a.y/c-d.y-e.y)};
-mxElbowEdgeHandler.prototype.redrawInnerBends=function(a,b){var c=this.graph.getModel().getGeometry(this.state.cell).points,c=c!=null?c[0]:null,c=c==null?new mxPoint(a.x+(b.x-a.x)/2,a.y+(b.y-a.y)/2):new mxPoint(this.graph.getView().scale*(c.x+this.graph.getView().translate.x+this.state.origin.x),this.graph.getView().scale*(c.y+this.graph.getView().translate.y+this.state.origin.y)),d=this.bends[1].bounds,e=d.width,d=d.height;if(this.handleImage==null)d=e=mxConstants.HANDLE_SIZE;var f=new mxRectangle(c.x-
-e/2,c.y-d/2,e,d);if(this.handleImage==null&&this.labelShape.node.style.visibility!="hidden"&&mxUtils.intersects(f,this.labelShape.bounds)){e=e+3;d=d+3;f=new mxRectangle(c.x-e/2,c.y-d/2,e,d)}this.bends[1].bounds=f;this.bends[1].reconfigure();this.bends[1].redraw()};function mxEdgeSegmentHandler(a){if(a!=null){this.state=a;this.init()}}mxEdgeSegmentHandler.prototype=new mxElbowEdgeHandler;mxEdgeSegmentHandler.prototype.constructor=mxEdgeSegmentHandler;
-mxEdgeSegmentHandler.prototype.getPreviewPoints=function(a){if(this.isSource||this.isTarget)return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this,arguments);this.convertPoint(a,false);var b=this.state.absolutePoints,c=b[0].clone();this.convertPoint(c,false);for(var d=[],e=1;e<b.length;e++){var f=b[e].clone();this.convertPoint(f,false);if(e==this.index)if(c.x==f.x){c.x=a.x;f.x=a.x}else{c.y=a.y;f.y=a.y}e<b.length-1&&d.push(f);c=f}if(d.length==1){c=this.state.view;e=this.state.getVisibleTerminalState(true);
-f=this.state.getVisibleTerminalState(false);if(f!=null&e!=null){var g=this.state.origin.x,h=this.state.origin.y;if(mxUtils.contains(f,d[0].x+g,d[0].y+h))b[1].y==b[2].y?d[0].y=c.getRoutingCenterY(e)-h:d[0].x=c.getRoutingCenterX(e)-g;else if(mxUtils.contains(e,d[0].x+g,d[0].y+h))b[1].y==b[0].y?d[0].y=c.getRoutingCenterY(f)-h:d[0].x=c.getRoutingCenterX(f)-g}}else d.length==0&&(d=[a]);return d};
-mxEdgeSegmentHandler.prototype.createBends=function(){var a=[],b=this.createHandleShape(0);this.initBend(b);b.node.style.cursor=mxConstants.CURSOR_BEND_HANDLE;mxEvent.redirectMouseEvents(b.node,this.graph,this.state);a.push(b);mxClient.IS_TOUCH&&b.node.setAttribute("pointer-events","none");var c=this.state.absolutePoints;if(this.graph.isCellBendable(this.state.cell)){if(this.points==null)this.points=[];for(var d=0;d<c.length-1;d++){b=this.createVirtualBend();a.push(b);b.node.style.cursor=c[d].x-c[d+
-1].x==0?"col-resize":"row-resize";this.points.push(new mxPoint(0,0));mxClient.IS_TOUCH&&b.node.setAttribute("pointer-events","none")}}b=this.createHandleShape(c.length);this.initBend(b);b.node.style.cursor=mxConstants.CURSOR_BEND_HANDLE;mxEvent.redirectMouseEvents(b.node,this.graph,this.state);a.push(b);mxClient.IS_TOUCH&&b.node.setAttribute("pointer-events","none");return a};
-mxEdgeSegmentHandler.prototype.redrawInnerBends=function(a,b){if(this.graph.isCellBendable(this.state.cell)){var c=mxConstants.HANDLE_SIZE,d=this.state.absolutePoints;if(d!=null&&d.length>1)for(var e=0;e<this.state.absolutePoints.length-1;e++)if(this.bends[e+1]!=null){var a=d[e],b=d[e+1],f=new mxPoint(a.x+(b.x-a.x)/2,a.y+(b.y-a.y)/2);this.bends[e+1].bounds=new mxRectangle(f.x-c/2,f.y-c/2,c,c);this.bends[e+1].reconfigure();this.bends[e+1].redraw()}}};
-mxEdgeSegmentHandler.prototype.connect=function(a,b,c,d,e){mxEdgeHandler.prototype.connect.apply(this,arguments);this.refresh()};mxEdgeSegmentHandler.prototype.changeTerminalPoint=function(a,b,c){mxEdgeHandler.prototype.changeTerminalPoint.apply(this,arguments);this.refresh()};
-mxEdgeSegmentHandler.prototype.changePoints=function(a,b){var b=[],c=this.abspoints;if(c.length>1)for(var d=c[0],e=c[1],f=2;f<c.length;f++){var g=c[f];if((Math.round(d.x)!=Math.round(e.x)||Math.round(e.x)!=Math.round(g.x))&&(Math.round(d.y)!=Math.round(e.y)||Math.round(e.y)!=Math.round(g.y))){d=e;e=e.clone();this.convertPoint(e,false);b.push(e)}e=g}mxElbowEdgeHandler.prototype.changePoints.apply(this,arguments);this.refresh()};
-mxEdgeSegmentHandler.prototype.refresh=function(){if(this.bends!=null){for(var a=0;a<this.bends.length;a++)if(this.bends[a]!=null){this.bends[a].destroy();this.bends[a]=null}this.bends=this.createBends()}};
-function mxKeyHandler(a,b){if(a!=null){this.graph=a;this.target=b||document.documentElement;this.normalKeys=[];this.shiftKeys=[];this.controlKeys=[];this.controlShiftKeys=[];mxEvent.addListener(this.target,"keydown",mxUtils.bind(this,function(a){this.keyDown(a)}));mxClient.IS_IE&&mxEvent.addListener(window,"unload",mxUtils.bind(this,function(){this.destroy()}))}}mxKeyHandler.prototype.graph=null;mxKeyHandler.prototype.target=null;mxKeyHandler.prototype.normalKeys=null;
-mxKeyHandler.prototype.shiftKeys=null;mxKeyHandler.prototype.controlKeys=null;mxKeyHandler.prototype.controlShiftKeys=null;mxKeyHandler.prototype.enabled=!0;mxKeyHandler.prototype.isEnabled=function(){return this.enabled};mxKeyHandler.prototype.setEnabled=function(a){this.enabled=a};mxKeyHandler.prototype.bindKey=function(a,b){this.normalKeys[a]=b};mxKeyHandler.prototype.bindShiftKey=function(a,b){this.shiftKeys[a]=b};mxKeyHandler.prototype.bindControlKey=function(a,b){this.controlKeys[a]=b};
-mxKeyHandler.prototype.bindControlShiftKey=function(a,b){this.controlShiftKeys[a]=b};mxKeyHandler.prototype.isControlDown=function(a){return mxEvent.isControlDown(a)};mxKeyHandler.prototype.getFunction=function(a){return a!=null?this.isControlDown(a)?mxEvent.isShiftDown(a)?this.controlShiftKeys[a.keyCode]:this.controlKeys[a.keyCode]:mxEvent.isShiftDown(a)?this.shiftKeys[a.keyCode]:this.normalKeys[a.keyCode]:null};
-mxKeyHandler.prototype.isGraphEvent=function(a){a=mxEvent.getSource(a);if(a==this.target||a.parentNode==this.target||this.graph.cellEditor!=null&&a==this.graph.cellEditor.textarea)return true;for(;a!=null;){if(a==this.graph.container)return true;a=a.parentNode}return false};
-mxKeyHandler.prototype.keyDown=function(a){if(this.graph.isEnabled()&&!mxEvent.isConsumed(a)&&this.isGraphEvent(a)&&this.isEnabled())if(a.keyCode==27)this.escape(a);else if(!this.graph.isEditing()){var b=this.getFunction(a);if(b!=null){b(a);mxEvent.consume(a)}}};mxKeyHandler.prototype.escape=function(a){this.graph.isEscapeEnabled()&&this.graph.escape(a)};mxKeyHandler.prototype.destroy=function(){this.target=null};
-function mxTooltipHandler(a,b){if(a!=null){this.graph=a;this.delay=b||500;this.graph.addMouseListener(this)}}mxTooltipHandler.prototype.zIndex=10005;mxTooltipHandler.prototype.graph=null;mxTooltipHandler.prototype.delay=null;mxTooltipHandler.prototype.hideOnHover=!1;mxTooltipHandler.prototype.enabled=!0;mxTooltipHandler.prototype.isEnabled=function(){return this.enabled};mxTooltipHandler.prototype.setEnabled=function(a){this.enabled=a};mxTooltipHandler.prototype.isHideOnHover=function(){return this.hideOnHover};
-mxTooltipHandler.prototype.setHideOnHover=function(a){this.hideOnHover=a};mxTooltipHandler.prototype.init=function(){if(document.body!=null){this.div=document.createElement("div");this.div.className="mxTooltip";this.div.style.visibility="hidden";this.div.style.zIndex=this.zIndex;document.body.appendChild(this.div);mxEvent.addListener(this.div,"mousedown",mxUtils.bind(this,function(){this.hideTooltip()}))}};mxTooltipHandler.prototype.mouseDown=function(a,b){this.reset(b,false);this.hideTooltip()};
-mxTooltipHandler.prototype.mouseMove=function(a,b){if(b.getX()!=this.lastX||b.getY()!=this.lastY){this.reset(b,true);(this.isHideOnHover()||b.getState()!=this.state||b.getSource()!=this.node&&(!this.stateSource||b.getState()!=null&&this.stateSource==(b.isSource(b.getState().shape)||!b.isSource(b.getState().text))))&&this.hideTooltip()}this.lastX=b.getX();this.lastY=b.getY()};mxTooltipHandler.prototype.mouseUp=function(a,b){this.reset(b,true);this.hideTooltip()};
-mxTooltipHandler.prototype.resetTimer=function(){if(this.thread!=null){window.clearTimeout(this.thread);this.thread=null}};
-mxTooltipHandler.prototype.reset=function(a,b){this.resetTimer();if(b&&this.isEnabled()&&a.getState()!=null&&(this.div==null||this.div.style.visibility=="hidden")){var c=a.getState(),d=a.getSource(),e=a.getX(),f=a.getY(),g=a.isSource(c.shape)||a.isSource(c.text);this.thread=window.setTimeout(mxUtils.bind(this,function(){if(!this.graph.isEditing()&&!this.graph.panningHandler.isMenuShowing()){this.show(this.graph.getTooltip(c,d,e,f),e,f);this.state=c;this.node=d;this.stateSource=g}}),this.delay)}};
-mxTooltipHandler.prototype.hide=function(){this.resetTimer();this.hideTooltip()};mxTooltipHandler.prototype.hideTooltip=function(){if(this.div!=null)this.div.style.visibility="hidden"};
-mxTooltipHandler.prototype.show=function(a,b,c){if(a!=null&&a.length>0){this.div==null&&this.init();var d=mxUtils.getScrollOrigin();this.div.style.left=b+d.x+"px";this.div.style.top=c+mxConstants.TOOLTIP_VERTICAL_OFFSET+d.y+"px";if(mxUtils.isNode(a)){this.div.innerHTML="";this.div.appendChild(a)}else this.div.innerHTML=a.replace(/\n/g,"<br>");this.div.style.visibility="";mxUtils.fit(this.div)}};
-mxTooltipHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);mxEvent.release(this.div);this.div!=null&&this.div.parentNode!=null&&this.div.parentNode.removeChild(this.div);this.div=null};function mxCellTracker(a,b,c){mxCellMarker.call(this,a,b);this.graph.addMouseListener(this);if(c!=null)this.getCell=c;mxClient.IS_IE&&mxEvent.addListener(window,"unload",mxUtils.bind(this,function(){this.destroy()}))}mxCellTracker.prototype=new mxCellMarker;
-mxCellTracker.prototype.constructor=mxCellTracker;mxCellTracker.prototype.mouseDown=function(){};mxCellTracker.prototype.mouseMove=function(a,b){this.isEnabled()&&this.process(b)};mxCellTracker.prototype.mouseUp=function(){this.reset()};mxCellTracker.prototype.destroy=function(){if(!this.destroyed){this.destroyed=true;this.graph.removeMouseListener(this);mxCellMarker.prototype.destroy.apply(this)}};
-function mxCellHighlight(a,b,c){if(a!=null){this.graph=a;this.highlightColor=b!=null?b:mxConstants.DEFAULT_VALID_COLOR;this.strokeWidth=c!=null?c:mxConstants.HIGHLIGHT_STROKEWIDTH;this.repaintHandler=mxUtils.bind(this,function(){this.repaint()});this.graph.getView().addListener(mxEvent.SCALE,this.repaintHandler);this.graph.getView().addListener(mxEvent.TRANSLATE,this.repaintHandler);this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE,this.repaintHandler);this.graph.getModel().addListener(mxEvent.CHANGE,
-this.repaintHandler);this.resetHandler=mxUtils.bind(this,function(){this.hide()});this.graph.getView().addListener(mxEvent.DOWN,this.resetHandler);this.graph.getView().addListener(mxEvent.UP,this.resetHandler)}}mxCellHighlight.prototype.keepOnTop=!1;mxCellHighlight.prototype.graph=!0;mxCellHighlight.prototype.state=null;mxCellHighlight.prototype.spacing=2;mxCellHighlight.prototype.resetHandler=null;
-mxCellHighlight.prototype.setHighlightColor=function(a){this.highlightColor=a;if(this.shape!=null)if(this.shape.dialect==mxConstants.DIALECT_SVG)this.shape.innerNode.setAttribute("stroke",a);else if(this.shape.dialect==mxConstants.DIALECT_VML)this.shape.node.strokecolor=a};
-mxCellHighlight.prototype.drawHighlight=function(){this.shape=this.createShape();this.repaint();!this.keepOnTop&&this.shape.node.parentNode.firstChild!=this.shape.node&&this.shape.node.parentNode.insertBefore(this.shape.node,this.shape.node.parentNode.firstChild);this.graph.model.isEdge(this.state.cell)&&mxUtils.repaintGraph(this.graph,this.shape.points[0])};
-mxCellHighlight.prototype.createShape=function(){var a=null,a=this.graph.model.isEdge(this.state.cell)?new mxPolyline(this.state.absolutePoints,this.highlightColor,this.strokeWidth):new mxRectangleShape(new mxRectangle,null,this.highlightColor,this.strokeWidth);a.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;a.init(this.graph.getView().getOverlayPane());mxEvent.redirectMouseEvents(a.node,this.graph,this.state);return a};
-mxCellHighlight.prototype.repaint=function(){if(this.state!=null&&this.shape!=null){this.graph.model.isEdge(this.state.cell)?this.shape.points=this.state.absolutePoints:this.shape.bounds=new mxRectangle(this.state.x-this.spacing,this.state.y-this.spacing,this.state.width+2*this.spacing,this.state.height+2*this.spacing);this.state.shape!=null&&this.shape.setCursor(this.state.shape.getCursor());var a=!this.graph.model.isEdge(this.state.cell)?Number(this.state.style[mxConstants.STYLE_ROTATION]||"0"):
-0;if(this.shape.dialect==mxConstants.DIALECT_SVG){this.shape.node.setAttribute("style","pointer-events:none;");if(a!=0){var b=this.state.getCenterX(),c=this.state.getCenterY();this.shape.node.setAttribute("transform","rotate("+a+" "+b+" "+c+")")}}else{this.shape.node.style.background="";if(a!=0)this.shape.node.rotation=a}this.shape.redraw()}};mxCellHighlight.prototype.hide=function(){this.highlight(null)};
-mxCellHighlight.prototype.highlight=function(a){if(this.state!=a){if(this.shape!=null){this.shape.destroy();this.shape=null}this.state=a;this.state!=null&&this.drawHighlight()}};
-mxCellHighlight.prototype.destroy=function(){this.graph.getView().removeListener(this.repaintHandler);this.graph.getModel().removeListener(this.repaintHandler);this.graph.getView().removeListener(this.resetHandler);this.graph.getModel().removeListener(this.resetHandler);if(this.shape!=null){this.shape.destroy();this.shape=null}};
-function mxDefaultKeyHandler(a){if(a!=null){this.editor=a;this.handler=new mxKeyHandler(a.graph);var b=this.handler.escape;this.handler.escape=function(c){b.apply(this,arguments);a.hideProperties();a.fireEvent(new mxEventObject(mxEvent.ESCAPE,"event",c))}}}mxDefaultKeyHandler.prototype.editor=null;mxDefaultKeyHandler.prototype.handler=null;
-mxDefaultKeyHandler.prototype.bindAction=function(a,b,c){var d=mxUtils.bind(this,function(){this.editor.execute(b)});c?this.handler.bindControlKey(a,d):this.handler.bindKey(a,d)};mxDefaultKeyHandler.prototype.destroy=function(){this.handler.destroy();this.handler=null};function mxDefaultPopupMenu(a){this.config=a}mxDefaultPopupMenu.prototype.imageBasePath=null;mxDefaultPopupMenu.prototype.config=null;
-mxDefaultPopupMenu.prototype.createMenu=function(a,b,c,d){if(this.config!=null){var e=this.createConditions(a,c,d);this.addItems(a,b,c,d,e,this.config.firstChild,null)}};
-mxDefaultPopupMenu.prototype.addItems=function(a,b,c,d,e,f,g){for(var h=false;f!=null;){if(f.nodeName=="add"){var k=f.getAttribute("if");if(k==null||e[k]){var k=f.getAttribute("as"),k=mxResources.get(k)||k,i=mxUtils.eval(mxUtils.getTextContent(f)),l=f.getAttribute("action"),m=f.getAttribute("icon"),n=f.getAttribute("iconCls");if(h){b.addSeparator(g);h=false}m!=null&&this.imageBasePath&&(m=this.imageBasePath+m);k=this.addAction(b,a,k,m,i,l,c,g,n);this.addItems(a,b,c,d,e,f.firstChild,k)}}else f.nodeName==
-"separator"&&(h=true);f=f.nextSibling}};mxDefaultPopupMenu.prototype.addAction=function(a,b,c,d,e,f,g,h,k){return a.addItem(c,d,function(a){typeof e=="function"&&e.call(b,b,g,a);f!=null&&b.execute(f,g,a)},h,k)};
-mxDefaultPopupMenu.prototype.createConditions=function(a,b,c){var d=a.graph.getModel(),e=d.getChildCount(b),f=[];f.nocell=b==null;f.ncells=a.graph.getSelectionCount()>1;f.notRoot=d.getRoot()!=d.getParent(a.graph.getDefaultParent());f.cell=b!=null;d=b!=null&&a.graph.getSelectionCount()==1;f.nonEmpty=d&&e>0;f.expandable=d&&a.graph.isCellFoldable(b,false);f.collapsable=d&&a.graph.isCellFoldable(b,true);f.validRoot=d&&a.graph.isValidRoot(b);f.emptyValidRoot=f.validRoot&&e==0;f.swimlane=d&&a.graph.isSwimlane(b);
-e=this.config.getElementsByTagName("condition");for(d=0;d<e.length;d++){var g=mxUtils.eval(mxUtils.getTextContent(e[d])),h=e[d].getAttribute("name");h!=null&&typeof g=="function"&&(f[h]=g(a,b,c))}return f};function mxDefaultToolbar(a,b){this.editor=b;a!=null&&b!=null&&this.init(a)}mxDefaultToolbar.prototype.editor=null;mxDefaultToolbar.prototype.toolbar=null;mxDefaultToolbar.prototype.resetHandler=null;mxDefaultToolbar.prototype.spacing=4;mxDefaultToolbar.prototype.connectOnDrop=!1;
-mxDefaultToolbar.prototype.init=function(a){if(a!=null){this.toolbar=new mxToolbar(a);this.toolbar.addListener(mxEvent.SELECT,mxUtils.bind(this,function(a,c){var d=c.getProperty("function");this.editor.insertFunction=d!=null?mxUtils.bind(this,function(){d.apply(this,arguments);this.toolbar.resetMode()}):null}));this.resetHandler=mxUtils.bind(this,function(){this.toolbar!=null&&this.toolbar.resetMode(true)});this.editor.graph.addListener(mxEvent.DOUBLE_CLICK,this.resetHandler);this.editor.addListener(mxEvent.ESCAPE,
-this.resetHandler)}};mxDefaultToolbar.prototype.addItem=function(a,b,c,d){var e=mxUtils.bind(this,function(){c!=null&&c.length>0&&this.editor.execute(c)});return this.toolbar.addItem(a,b,e,d)};mxDefaultToolbar.prototype.addSeparator=function(a){a=a||mxClient.imageBasePath+"/separator.gif";this.toolbar.addSeparator(a)};mxDefaultToolbar.prototype.addCombo=function(){return this.toolbar.addCombo()};mxDefaultToolbar.prototype.addActionCombo=function(a){return this.toolbar.addActionCombo(a)};
-mxDefaultToolbar.prototype.addActionOption=function(a,b,c){var d=mxUtils.bind(this,function(){this.editor.execute(c)});this.addOption(a,b,d)};mxDefaultToolbar.prototype.addOption=function(a,b,c){return this.toolbar.addOption(a,b,c)};mxDefaultToolbar.prototype.addMode=function(a,b,c,d,e){var f=mxUtils.bind(this,function(){this.editor.setMode(c);e!=null&&e(this.editor)});return this.toolbar.addSwitchMode(a,b,f,d)};
-mxDefaultToolbar.prototype.addPrototype=function(a,b,c,d,e,f){var g=function(){return typeof c=="function"?c():c!=null?c.clone():null},h=mxUtils.bind(this,function(a,b){typeof e=="function"?e(this.editor,g(),a,b):this.drop(g(),a,b);this.toolbar.resetMode();mxEvent.consume(a)}),a=this.toolbar.addMode(a,b,h,d,null,f);this.installDropHandler(a,function(a,b,c){h(b,c)});return a};
-mxDefaultToolbar.prototype.drop=function(a,b,c){var d=this.editor.graph,e=d.getModel();if(c==null||e.isEdge(c)||!this.connectOnDrop||!d.isCellConnectable(c)){for(;c!=null&&!d.isValidDropTarget(c,[a],b);)c=e.getParent(c);this.insert(a,b,c)}else this.connect(a,b,c)};
-mxDefaultToolbar.prototype.insert=function(a,b,c){var d=this.editor.graph;if(d.canImportCell(a)){var e=mxEvent.getClientX(b),f=mxEvent.getClientY(b),e=mxUtils.convertPoint(d.container,e,f);return d.isSplitEnabled()&&d.isSplitTarget(c,[a],b)?d.splitEdge(c,[a],null,e.x,e.y):this.editor.addVertex(c,a,e.x,e.y)}return null};
-mxDefaultToolbar.prototype.connect=function(a,b,c){var b=this.editor.graph,d=b.getModel();if(c!=null&&b.isCellConnectable(a)&&b.isEdgeValid(null,c,a)){var e=null;d.beginUpdate();try{var f=d.getGeometry(c),g=d.getGeometry(a).clone();g.x=f.x+(f.width-g.width)/2;g.y=f.y+(f.height-g.height)/2;var h=this.spacing*b.gridSize,k=d.getDirectedEdgeCount(c,true)*20;this.editor.horizontalFlow?g.x=g.x+((g.width+f.width)/2+h+k):g.y=g.y+((g.height+f.height)/2+h+k);a.setGeometry(g);var i=d.getParent(c);b.addCell(a,
-i);b.constrainChild(a);e=this.editor.createEdge(c,a);if(d.getGeometry(e)==null){var l=new mxGeometry;l.relative=true;d.setGeometry(e,l)}b.addEdge(e,i,c,a)}finally{d.endUpdate()}b.setSelectionCells([a,e]);b.scrollCellToVisible(a)}};
-mxDefaultToolbar.prototype.installDropHandler=function(a,b){var c=document.createElement("img");c.setAttribute("src",a.getAttribute("src"));var d=mxUtils.bind(this,function(){c.style.width=2*a.offsetWidth+"px";c.style.height=2*a.offsetHeight+"px";mxUtils.makeDraggable(a,this.editor.graph,b,c);mxEvent.removeListener(c,"load",d)});mxClient.IS_IE?d():mxEvent.addListener(c,"load",d)};
-mxDefaultToolbar.prototype.destroy=function(){if(this.resetHandler!=null){this.editor.graph.removeListener("dblclick",this.resetHandler);this.editor.removeListener("escape",this.resetHandler);this.resetHandler=null}if(this.toolbar!=null){this.toolbar.destroy();this.toolbar=null}};
-function mxEditor(a){this.actions=[];this.addActions();if(document.body!=null){this.cycleAttributeValues=[];this.popupHandler=new mxDefaultPopupMenu;this.undoManager=new mxUndoManager;this.graph=this.createGraph();this.toolbar=this.createToolbar();this.keyHandler=new mxDefaultKeyHandler(this);this.configure(a);this.graph.swimlaneIndicatorColorAttribute=this.cycleAttributeName;if(!mxClient.IS_LOCAL&&this.urlInit!=null)this.session=this.createSession();if(this.onInit!=null)this.onInit();mxClient.IS_IE&&
-mxEvent.addListener(window,"unload",mxUtils.bind(this,function(){this.destroy()}))}}mxLoadResources&&mxResources.add(mxClient.basePath+"/resources/editor");mxEditor.prototype=new mxEventSource;mxEditor.prototype.constructor=mxEditor;mxEditor.prototype.askZoomResource="none"!=mxClient.language?"askZoom":"";mxEditor.prototype.lastSavedResource="none"!=mxClient.language?"lastSaved":"";mxEditor.prototype.currentFileResource="none"!=mxClient.language?"currentFile":"";
-mxEditor.prototype.propertiesResource="none"!=mxClient.language?"properties":"";mxEditor.prototype.tasksResource="none"!=mxClient.language?"tasks":"";mxEditor.prototype.helpResource="none"!=mxClient.language?"help":"";mxEditor.prototype.outlineResource="none"!=mxClient.language?"outline":"";mxEditor.prototype.outline=null;mxEditor.prototype.graph=null;mxEditor.prototype.graphRenderHint=null;mxEditor.prototype.toolbar=null;mxEditor.prototype.session=null;mxEditor.prototype.status=null;
-mxEditor.prototype.popupHandler=null;mxEditor.prototype.undoManager=null;mxEditor.prototype.keyHandler=null;mxEditor.prototype.actions=null;mxEditor.prototype.dblClickAction="edit";mxEditor.prototype.swimlaneRequired=!1;mxEditor.prototype.disableContextMenu=!0;mxEditor.prototype.insertFunction=null;mxEditor.prototype.forcedInserting=!1;mxEditor.prototype.templates=null;mxEditor.prototype.defaultEdge=null;mxEditor.prototype.defaultEdgeStyle=null;mxEditor.prototype.defaultGroup=null;
-mxEditor.prototype.groupBorderSize=null;mxEditor.prototype.filename=null;mxEditor.prototype.linefeed="&#xa;";mxEditor.prototype.postParameterName="xml";mxEditor.prototype.escapePostData=!0;mxEditor.prototype.urlPost=null;mxEditor.prototype.urlImage=null;mxEditor.prototype.urlInit=null;mxEditor.prototype.urlNotify=null;mxEditor.prototype.urlPoll=null;mxEditor.prototype.horizontalFlow=!1;mxEditor.prototype.layoutDiagram=!1;mxEditor.prototype.swimlaneSpacing=0;mxEditor.prototype.maintainSwimlanes=!1;
-mxEditor.prototype.layoutSwimlanes=!1;mxEditor.prototype.cycleAttributeValues=null;mxEditor.prototype.cycleAttributeIndex=0;mxEditor.prototype.cycleAttributeName="fillColor";mxEditor.prototype.tasks=null;mxEditor.prototype.tasksWindowImage=null;mxEditor.prototype.tasksTop=20;mxEditor.prototype.help=null;mxEditor.prototype.helpWindowImage=null;mxEditor.prototype.urlHelp=null;mxEditor.prototype.helpWidth=300;mxEditor.prototype.helpHeight=260;mxEditor.prototype.propertiesWidth=240;
-mxEditor.prototype.propertiesHeight=null;mxEditor.prototype.movePropertiesDialog=!1;mxEditor.prototype.validating=!1;mxEditor.prototype.modified=!1;mxEditor.prototype.isModified=function(){return this.modified};mxEditor.prototype.setModified=function(a){this.modified=a};
-mxEditor.prototype.addActions=function(){this.addAction("save",function(a){a.save()});this.addAction("print",function(a){(new mxPrintPreview(a.graph,1)).open()});this.addAction("show",function(a){mxUtils.show(a.graph,null,10,10)});this.addAction("exportImage",function(a){var b=a.getUrlImage();if(b==null||mxClient.IS_LOCAL)a.execute("show");else{var c=mxUtils.getViewXml(a.graph,1),c=mxUtils.getXml(c,"\n");mxUtils.submit(b,a.postParameterName+"="+encodeURIComponent(c),document,"_blank")}});this.addAction("refresh",
-function(a){a.graph.refresh()});this.addAction("cut",function(a){a.graph.isEnabled()&&mxClipboard.cut(a.graph)});this.addAction("copy",function(a){a.graph.isEnabled()&&mxClipboard.copy(a.graph)});this.addAction("paste",function(a){a.graph.isEnabled()&&mxClipboard.paste(a.graph)});this.addAction("delete",function(a){a.graph.isEnabled()&&a.graph.removeCells()});this.addAction("group",function(a){a.graph.isEnabled()&&a.graph.setSelectionCell(a.groupCells())});this.addAction("ungroup",function(a){a.graph.isEnabled()&&
-a.graph.setSelectionCells(a.graph.ungroupCells())});this.addAction("removeFromParent",function(a){a.graph.isEnabled()&&a.graph.removeCellsFromParent()});this.addAction("undo",function(a){a.graph.isEnabled()&&a.undo()});this.addAction("redo",function(a){a.graph.isEnabled()&&a.redo()});this.addAction("zoomIn",function(a){a.graph.zoomIn()});this.addAction("zoomOut",function(a){a.graph.zoomOut()});this.addAction("actualSize",function(a){a.graph.zoomActual()});this.addAction("fit",function(a){a.graph.fit()});
-this.addAction("showProperties",function(a,b){a.showProperties(b)});this.addAction("selectAll",function(a){a.graph.isEnabled()&&a.graph.selectAll()});this.addAction("selectNone",function(a){a.graph.isEnabled()&&a.graph.clearSelection()});this.addAction("selectVertices",function(a){a.graph.isEnabled()&&a.graph.selectVertices()});this.addAction("selectEdges",function(a){a.graph.isEnabled()&&a.graph.selectEdges()});this.addAction("edit",function(a,b){a.graph.isEnabled()&&a.graph.isCellEditable(b)&&a.graph.startEditingAtCell(b)});
-this.addAction("toBack",function(a){a.graph.isEnabled()&&a.graph.orderCells(true)});this.addAction("toFront",function(a){a.graph.isEnabled()&&a.graph.orderCells(false)});this.addAction("enterGroup",function(a,b){a.graph.enterGroup(b)});this.addAction("exitGroup",function(a){a.graph.exitGroup()});this.addAction("home",function(a){a.graph.home()});this.addAction("selectPrevious",function(a){a.graph.isEnabled()&&a.graph.selectPreviousCell()});this.addAction("selectNext",function(a){a.graph.isEnabled()&&
-a.graph.selectNextCell()});this.addAction("selectParent",function(a){a.graph.isEnabled()&&a.graph.selectParentCell()});this.addAction("selectChild",function(a){a.graph.isEnabled()&&a.graph.selectChildCell()});this.addAction("collapse",function(a){a.graph.isEnabled()&&a.graph.foldCells(true)});this.addAction("collapseAll",function(a){if(a.graph.isEnabled()){var b=a.graph.getChildVertices();a.graph.foldCells(true,false,b)}});this.addAction("expand",function(a){a.graph.isEnabled()&&a.graph.foldCells(false)});
-this.addAction("expandAll",function(a){if(a.graph.isEnabled()){var b=a.graph.getChildVertices();a.graph.foldCells(false,false,b)}});this.addAction("bold",function(a){a.graph.isEnabled()&&a.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE,mxConstants.FONT_BOLD)});this.addAction("italic",function(a){a.graph.isEnabled()&&a.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE,mxConstants.FONT_ITALIC)});this.addAction("underline",function(a){a.graph.isEnabled()&&a.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE,
-mxConstants.FONT_UNDERLINE)});this.addAction("shadow",function(a){a.graph.isEnabled()&&a.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE,mxConstants.FONT_SHADOW)});this.addAction("alignCellsLeft",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_LEFT)});this.addAction("alignCellsCenter",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_CENTER)});this.addAction("alignCellsRight",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_RIGHT)});
-this.addAction("alignCellsTop",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_TOP)});this.addAction("alignCellsMiddle",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_MIDDLE)});this.addAction("alignCellsBottom",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_BOTTOM)});this.addAction("alignFontLeft",function(a){a.graph.setCellStyles(mxConstants.STYLE_ALIGN,mxConstants.ALIGN_LEFT)});this.addAction("alignFontCenter",function(a){a.graph.isEnabled()&&
-a.graph.setCellStyles(mxConstants.STYLE_ALIGN,mxConstants.ALIGN_CENTER)});this.addAction("alignFontRight",function(a){a.graph.isEnabled()&&a.graph.setCellStyles(mxConstants.STYLE_ALIGN,mxConstants.ALIGN_RIGHT)});this.addAction("alignFontTop",function(a){a.graph.isEnabled()&&a.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN,mxConstants.ALIGN_TOP)});this.addAction("alignFontMiddle",function(a){a.graph.isEnabled()&&a.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN,mxConstants.ALIGN_MIDDLE)});
-this.addAction("alignFontBottom",function(a){a.graph.isEnabled()&&a.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN,mxConstants.ALIGN_BOTTOM)});this.addAction("zoom",function(a){var b=a.graph.getView().scale*100,b=parseFloat(mxUtils.prompt(mxResources.get(a.askZoomResource)||a.askZoomResource,b))/100;isNaN(b)||a.graph.getView().setScale(b)});this.addAction("toggleTasks",function(a){a.tasks!=null?a.tasks.setVisible(!a.tasks.isVisible()):a.showTasks()});this.addAction("toggleHelp",function(a){a.help!=
-null?a.help.setVisible(!a.help.isVisible()):a.showHelp()});this.addAction("toggleOutline",function(a){a.outline==null?a.showOutline():a.outline.setVisible(!a.outline.isVisible())});this.addAction("toggleConsole",function(){mxLog.setVisible(!mxLog.isVisible())})};mxEditor.prototype.createSession=function(){return this.connect(this.urlInit,this.urlPoll,this.urlNotify,mxUtils.bind(this,function(a){this.fireEvent(new mxEventObject(mxEvent.SESSION,"session",a))}))};
-mxEditor.prototype.configure=function(a){if(a!=null){(new mxCodec(a.ownerDocument)).decode(a,this);this.resetHistory()}};mxEditor.prototype.resetFirstTime=function(){document.cookie="mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/"};mxEditor.prototype.resetHistory=function(){this.lastSnapshot=(new Date).getTime();this.undoManager.clear();this.ignoredChanges=0;this.setModified(false)};mxEditor.prototype.addAction=function(a,b){this.actions[a]=b};
-mxEditor.prototype.execute=function(a,b,c){var d=this.actions[a];if(d!=null)try{var e=arguments;e[0]=this;d.apply(this,e)}catch(f){mxUtils.error("Cannot execute "+a+": "+f.message,280,true);throw f;}else mxUtils.error("Cannot find action "+a,280,true)};mxEditor.prototype.addTemplate=function(a,b){this.templates[a]=b};mxEditor.prototype.getTemplate=function(a){return this.templates[a]};
-mxEditor.prototype.createGraph=function(){var a=new mxGraph(null,null,this.graphRenderHint);a.setTooltips(true);a.setPanning(true);this.installDblClickHandler(a);this.installUndoHandler(a);this.installDrillHandler(a);this.installChangeHandler(a);this.installInsertHandler(a);a.panningHandler.factoryMethod=mxUtils.bind(this,function(a,c,d){return this.createPopupMenu(a,c,d)});a.connectionHandler.factoryMethod=mxUtils.bind(this,function(a,c){return this.createEdge(a,c)});this.createSwimlaneManager(a);
-this.createLayoutManager(a);return a};mxEditor.prototype.createSwimlaneManager=function(a){a=new mxSwimlaneManager(a,false);a.isHorizontal=mxUtils.bind(this,function(){return this.horizontalFlow});a.isEnabled=mxUtils.bind(this,function(){return this.maintainSwimlanes});return a};
-mxEditor.prototype.createLayoutManager=function(a){var b=new mxLayoutManager(a),c=this;b.getLayout=function(b){var e=null,f=c.graph.getModel();if(f.getParent(b)!=null)if(c.layoutSwimlanes&&a.isSwimlane(b)){if(c.swimlaneLayout==null)c.swimlaneLayout=c.createSwimlaneLayout();e=c.swimlaneLayout}else if(c.layoutDiagram&&(a.isValidRoot(b)||f.getParent(f.getParent(b))==null)){if(c.diagramLayout==null)c.diagramLayout=c.createDiagramLayout();e=c.diagramLayout}return e};return b};
-mxEditor.prototype.setGraphContainer=function(a){if(this.graph.container==null){this.graph.init(a);this.rubberband=new mxRubberband(this.graph);this.disableContextMenu&&mxEvent.disableContextMenu(a);mxClient.IS_QUIRKS&&new mxDivResizer(a)}};mxEditor.prototype.installDblClickHandler=function(a){a.addListener(mxEvent.DOUBLE_CLICK,mxUtils.bind(this,function(b,c){var d=c.getProperty("cell");if(d!=null&&a.isEnabled()&&this.dblClickAction!=null){this.execute(this.dblClickAction,d);c.consume()}}))};
-mxEditor.prototype.installUndoHandler=function(a){var b=mxUtils.bind(this,function(a,b){this.undoManager.undoableEditHappened(b.getProperty("edit"))});a.getModel().addListener(mxEvent.UNDO,b);a.getView().addListener(mxEvent.UNDO,b);b=function(b,d){var e=d.getProperty("edit").changes;a.setSelectionCells(a.getSelectionCellsForChanges(e))};this.undoManager.addListener(mxEvent.UNDO,b);this.undoManager.addListener(mxEvent.REDO,b)};
-mxEditor.prototype.installDrillHandler=function(a){var b=mxUtils.bind(this,function(){this.fireEvent(new mxEventObject(mxEvent.ROOT))});a.getView().addListener(mxEvent.DOWN,b);a.getView().addListener(mxEvent.UP,b)};
-mxEditor.prototype.installChangeHandler=function(a){var b=mxUtils.bind(this,function(b,d){this.setModified(true);this.validating==true&&a.validateGraph();for(var e=d.getProperty("edit").changes,f=0;f<e.length;f++){var g=e[f];if(g instanceof mxRootChange||g instanceof mxValueChange&&g.cell==this.graph.model.root||g instanceof mxCellAttributeChange&&g.cell==this.graph.model.root){this.fireEvent(new mxEventObject(mxEvent.ROOT));break}}});a.getModel().addListener(mxEvent.CHANGE,b)};
-mxEditor.prototype.installInsertHandler=function(a){var b=this;a.addMouseListener({mouseDown:function(a,d){if(b.insertFunction!=null&&!d.isPopupTrigger()&&(b.forcedInserting||d.getState()==null)){b.graph.clearSelection();b.insertFunction(d.getEvent(),d.getCell());this.isActive=true;d.consume()}},mouseMove:function(a,b){this.isActive&&b.consume()},mouseUp:function(a,b){if(this.isActive){this.isActive=false;b.consume()}}})};
-mxEditor.prototype.createDiagramLayout=function(){var a=this.graph.gridSize,b=new mxStackLayout(this.graph,!this.horizontalFlow,this.swimlaneSpacing,2*a,2*a);b.isVertexIgnored=function(a){return!b.graph.isSwimlane(a)};return b};mxEditor.prototype.createSwimlaneLayout=function(){return new mxCompactTreeLayout(this.graph,this.horizontalFlow)};mxEditor.prototype.createToolbar=function(){return new mxDefaultToolbar(null,this)};
-mxEditor.prototype.setToolbarContainer=function(a){this.toolbar.init(a);mxClient.IS_QUIRKS&&new mxDivResizer(a)};
-mxEditor.prototype.setStatusContainer=function(a){if(this.status==null){this.status=a;this.addListener(mxEvent.SAVE,mxUtils.bind(this,function(){var a=(new Date).toLocaleString();this.setStatus((mxResources.get(this.lastSavedResource)||this.lastSavedResource)+": "+a)}));this.addListener(mxEvent.OPEN,mxUtils.bind(this,function(){this.setStatus((mxResources.get(this.currentFileResource)||this.currentFileResource)+": "+this.filename)}));mxClient.IS_QUIRKS&&new mxDivResizer(a)}};
-mxEditor.prototype.setStatus=function(a){if(this.status!=null&&a!=null)this.status.innerHTML=a};mxEditor.prototype.setTitleContainer=function(a){this.addListener(mxEvent.ROOT,mxUtils.bind(this,function(){a.innerHTML=this.getTitle()}));mxClient.IS_QUIRKS&&new mxDivResizer(a)};mxEditor.prototype.treeLayout=function(a,b){a!=null&&(new mxCompactTreeLayout(this.graph,b)).execute(a)};
-mxEditor.prototype.getTitle=function(){for(var a="",b=this.graph,c=b.getCurrentRoot();c!=null&&b.getModel().getParent(b.getModel().getParent(c))!=null;){b.isValidRoot(c)&&(a=" > "+b.convertValueToString(c)+a);c=b.getModel().getParent(c)}return this.getRootTitle()+a};mxEditor.prototype.getRootTitle=function(){return this.graph.convertValueToString(this.graph.getModel().getRoot())};mxEditor.prototype.undo=function(){this.undoManager.undo()};mxEditor.prototype.redo=function(){this.undoManager.redo()};
-mxEditor.prototype.groupCells=function(){var a=this.groupBorderSize!=null?this.groupBorderSize:this.graph.gridSize;return this.graph.groupCells(this.createGroup(),a)};mxEditor.prototype.createGroup=function(){return this.graph.getModel().cloneCell(this.defaultGroup)};mxEditor.prototype.open=function(a){if(a!=null){this.readGraphModel(mxUtils.load(a).getXml().documentElement);this.filename=a;this.fireEvent(new mxEventObject(mxEvent.OPEN,"filename",a))}};
-mxEditor.prototype.readGraphModel=function(a){(new mxCodec(a.ownerDocument)).decode(a,this.graph.getModel());this.resetHistory()};mxEditor.prototype.save=function(a,b){a=a||this.getUrlPost();if(a!=null&&a.length>0){var c=this.writeGraphModel(b);this.postDiagram(a,c);this.setModified(false)}this.fireEvent(new mxEventObject(mxEvent.SAVE,"url",a))};
-mxEditor.prototype.postDiagram=function(a,b){this.escapePostData&&(b=encodeURIComponent(b));mxUtils.post(a,this.postParameterName+"="+b,mxUtils.bind(this,function(c){this.fireEvent(new mxEventObject(mxEvent.POST,"request",c,"url",a,"data",b))}))};mxEditor.prototype.writeGraphModel=function(a){var a=a!=null?a:this.linefeed,b=(new mxCodec).encode(this.graph.getModel());return mxUtils.getXml(b,a)};mxEditor.prototype.getUrlPost=function(){return this.urlPost};mxEditor.prototype.getUrlImage=function(){return this.urlImage};
-mxEditor.prototype.connect=function(a,b,c,d){var e=null;if(!mxClient.IS_LOCAL){e=new mxSession(this.graph.getModel(),a,b,c);e.addListener(mxEvent.RECEIVE,mxUtils.bind(this,function(a,b){b.getProperty("node").getAttribute("namespace")!=null&&this.resetHistory()}));e.addListener(mxEvent.DISCONNECT,d);e.addListener(mxEvent.CONNECT,d);e.addListener(mxEvent.NOTIFY,d);e.addListener(mxEvent.GET,d);e.start()}return e};
-mxEditor.prototype.swapStyles=function(a,b){var c=this.graph.getStylesheet().styles[b];this.graph.getView().getStylesheet().putCellStyle(b,this.graph.getStylesheet().styles[a]);this.graph.getStylesheet().putCellStyle(a,c);this.graph.refresh()};
-mxEditor.prototype.showProperties=function(a){a=a||this.graph.getSelectionCell();if(a==null){a=this.graph.getCurrentRoot();a==null&&(a=this.graph.getModel().getRoot())}if(a!=null){this.graph.stopEditing(true);var b=mxUtils.getOffset(this.graph.container),c=b.x+10,b=b.y;if(this.properties!=null&&!this.movePropertiesDialog){c=this.properties.getX();b=this.properties.getY()}else{var d=this.graph.getCellBounds(a);if(d!=null){c=c+(d.x+Math.min(200,d.width));b=b+d.y}}this.hideProperties();a=this.createProperties(a);
-if(a!=null){this.properties=new mxWindow(mxResources.get(this.propertiesResource)||this.propertiesResource,a,c,b,this.propertiesWidth,this.propertiesHeight,false);this.properties.setVisible(true)}}};mxEditor.prototype.isPropertiesVisible=function(){return this.properties!=null};
-mxEditor.prototype.createProperties=function(a){var b=this.graph.getModel(),c=b.getValue(a);if(mxUtils.isNode(c)){var d=new mxForm("properties");d.addText("ID",a.getId()).setAttribute("readonly","true");var e=null,f=null,g=null,h=null,k=null;if(b.isVertex(a)){e=b.getGeometry(a);if(e!=null){f=d.addText("top",e.y);g=d.addText("left",e.x);h=d.addText("width",e.width);k=d.addText("height",e.height)}}for(var i=b.getStyle(a),l=d.addText("Style",i||""),m=c.attributes,n=[],c=0;c<m.length;c++)n[c]=d.addTextarea(m[c].nodeName,
-m[c].nodeValue,m[c].nodeName=="label"?4:2);c=mxUtils.bind(this,function(){this.hideProperties();b.beginUpdate();try{if(e!=null){e=e.clone();e.x=parseFloat(g.value);e.y=parseFloat(f.value);e.width=parseFloat(h.value);e.height=parseFloat(k.value);b.setGeometry(a,e)}l.value.length>0?b.setStyle(a,l.value):b.setStyle(a,null);for(var c=0;c<m.length;c++){var d=new mxCellAttributeChange(a,m[c].nodeName,n[c].value);b.execute(d)}this.graph.isAutoSizeCell(a)&&this.graph.updateCellSize(a)}finally{b.endUpdate()}});
-i=mxUtils.bind(this,function(){this.hideProperties()});d.addButtons(c,i);return d.table}return null};mxEditor.prototype.hideProperties=function(){if(this.properties!=null){this.properties.destroy();this.properties=null}};
-mxEditor.prototype.showTasks=function(){if(this.tasks==null){var a=document.createElement("div");a.style.padding="4px";a.style.paddingLeft="20px";var b=document.body.clientWidth,b=new mxWindow(mxResources.get(this.tasksResource)||this.tasksResource,a,b-220,this.tasksTop,200);b.setClosable(true);b.destroyOnClose=false;var c=mxUtils.bind(this,function(){mxEvent.release(a);a.innerHTML="";this.createTasks(a)});this.graph.getModel().addListener(mxEvent.CHANGE,c);this.graph.getSelectionModel().addListener(mxEvent.CHANGE,
-c);this.graph.addListener(mxEvent.ROOT,c);this.tasksWindowImage!=null&&b.setImage(this.tasksWindowImage);this.tasks=b;this.createTasks(a)}this.tasks.setVisible(true)};mxEditor.prototype.refreshTasks=function(a){if(this.tasks!=null){a=this.tasks.content;mxEvent.release(a);a.innerHTML="";this.createTasks(a)}};mxEditor.prototype.createTasks=function(){};
-mxEditor.prototype.showHelp=function(){if(this.help==null){var a=document.createElement("iframe");a.setAttribute("src",mxResources.get("urlHelp")||this.urlHelp);a.setAttribute("height","100%");a.setAttribute("width","100%");a.setAttribute("frameBorder","0");a.style.backgroundColor="white";var b=document.body.clientWidth,c=document.body.clientHeight||document.documentElement.clientHeight,d=new mxWindow(mxResources.get(this.helpResource)||this.helpResource,a,(b-this.helpWidth)/2,(c-this.helpHeight)/
-3,this.helpWidth,this.helpHeight);d.setMaximizable(true);d.setClosable(true);d.destroyOnClose=false;d.setResizable(true);this.helpWindowImage!=null&&d.setImage(this.helpWindowImage);if(mxClient.IS_NS){b=function(){a.setAttribute("height",d.div.offsetHeight-26+"px")};d.addListener(mxEvent.RESIZE_END,b);d.addListener(mxEvent.MAXIMIZE,b);d.addListener(mxEvent.NORMALIZE,b);d.addListener(mxEvent.SHOW,b)}this.help=d}this.help.setVisible(true)};
-mxEditor.prototype.showOutline=function(){if(this.outline==null){var a=document.createElement("div");a.style.overflow="hidden";a.style.width="100%";a.style.height="100%";a.style.background="white";a.style.cursor="move";var b=new mxWindow(mxResources.get(this.outlineResource)||this.outlineResource,a,600,480,200,200,false),c=new mxOutline(this.graph,a);b.setClosable(true);b.setResizable(true);b.destroyOnClose=false;b.addListener(mxEvent.RESIZE_END,function(){c.update()});this.outline=b;this.outline.outline=
-c}this.outline.setVisible(true);this.outline.outline.update(true)};mxEditor.prototype.setMode=function(a){if(a=="select"){this.graph.panningHandler.useLeftButtonForPanning=false;this.graph.setConnectable(false)}else if(a=="connect"){this.graph.panningHandler.useLeftButtonForPanning=false;this.graph.setConnectable(true)}else if(a=="pan"){this.graph.panningHandler.useLeftButtonForPanning=true;this.graph.setConnectable(false)}};
-mxEditor.prototype.createPopupMenu=function(a,b,c){this.popupHandler.createMenu(this,a,b,c)};mxEditor.prototype.createEdge=function(){var a=null;if(this.defaultEdge!=null)a=this.graph.getModel().cloneCell(this.defaultEdge);else{a=new mxCell("");a.setEdge(true);var b=new mxGeometry;b.relative=true;a.setGeometry(b)}b=this.getEdgeStyle();b!=null&&a.setStyle(b);return a};mxEditor.prototype.getEdgeStyle=function(){return this.defaultEdgeStyle};
-mxEditor.prototype.consumeCycleAttribute=function(a){return this.cycleAttributeValues!=null&&this.cycleAttributeValues.length>0&&this.graph.isSwimlane(a)?this.cycleAttributeValues[this.cycleAttributeIndex++%this.cycleAttributeValues.length]:null};mxEditor.prototype.cycleAttribute=function(a){if(this.cycleAttributeName!=null){var b=this.consumeCycleAttribute(a);b!=null&&a.setStyle(a.getStyle()+";"+this.cycleAttributeName+"="+b)}};
-mxEditor.prototype.addVertex=function(a,b,c,d){for(var e=this.graph.getModel();a!=null&&!this.graph.isValidDropTarget(a);)a=e.getParent(a);var a=a!=null?a:this.graph.getSwimlaneAt(c,d),f=this.graph.getView().scale,g=e.getGeometry(b),h=e.getGeometry(a);if(this.graph.isSwimlane(b)&&!this.graph.swimlaneNesting)a=null;else{if(a==null&&this.swimlaneRequired)return null;if(a!=null&&h!=null){var k=this.graph.getView().getState(a);if(k!=null){c=c-k.origin.x*f;d=d-k.origin.y*f;if(this.graph.isConstrainedMoving){var h=
-g.width,i=g.height,l=k.x+k.width;c+h>l&&(c=c-(c+h-l));l=k.y+k.height;d+i>l&&(d=d-(d+i-l))}}else if(h!=null){c=c-h.x*f;d=d-h.y*f}}}g=g.clone();g.x=this.graph.snap(c/f-this.graph.getView().translate.x-this.graph.gridSize/2);g.y=this.graph.snap(d/f-this.graph.getView().translate.y-this.graph.gridSize/2);b.setGeometry(g);a==null&&(a=this.graph.getDefaultParent());this.cycleAttribute(b);this.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX,"vertex",b,"parent",a));e.beginUpdate();try{b=this.graph.addCell(b,
-a);if(b!=null){this.graph.constrainChild(b);this.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX,"vertex",b))}}finally{e.endUpdate()}if(b!=null){this.graph.setSelectionCell(b);this.graph.scrollCellToVisible(b);this.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX,"vertex",b))}return b};
-mxEditor.prototype.destroy=function(){if(!this.destroyed){this.destroyed=true;this.tasks!=null&&this.tasks.destroy();this.outline!=null&&this.outline.destroy();this.properties!=null&&this.properties.destroy();this.keyHandler!=null&&this.keyHandler.destroy();this.rubberband!=null&&this.rubberband.destroy();this.toolbar!=null&&this.toolbar.destroy();this.graph!=null&&this.graph.destroy();this.templates=this.status=null}};
-var mxCodecRegistry={codecs:[],aliases:[],register:function(a){if(a!=null){var b=a.getName();mxCodecRegistry.codecs[b]=a;var c=mxUtils.getFunctionName(a.template.constructor);c!=b&&mxCodecRegistry.addAlias(c,b)}return a},addAlias:function(a,b){mxCodecRegistry.aliases[a]=b},getCodec:function(a){var b=null;if(a!=null){var b=mxUtils.getFunctionName(a),c=mxCodecRegistry.aliases[b];c!=null&&(b=c);b=mxCodecRegistry.codecs[b];if(b==null)try{b=new mxObjectCodec(new a);mxCodecRegistry.register(b)}catch(d){}}return b}};
-function mxCodec(a){this.document=a||mxUtils.createXmlDocument();this.objects=[]}mxCodec.prototype.document=null;mxCodec.prototype.objects=null;mxCodec.prototype.encodeDefaults=!1;mxCodec.prototype.putObject=function(a,b){return this.objects[a]=b};mxCodec.prototype.getObject=function(a){var b=null;if(a!=null){b=this.objects[a];if(b==null){b=this.lookup(a);if(b==null){a=this.getElementById(a);a!=null&&(b=this.decode(a))}}}return b};mxCodec.prototype.lookup=function(){return null};
-mxCodec.prototype.getElementById=function(a,b){return mxUtils.findNodeByAttribute(this.document.documentElement,b!=null?b:"id",a)};mxCodec.prototype.getId=function(a){var b=null;if(a!=null){b=this.reference(a);if(b==null&&a instanceof mxCell){b=a.getId();if(b==null){b=mxCellPath.create(a);b.length==0&&(b="root")}}}return b};mxCodec.prototype.reference=function(){return null};
-mxCodec.prototype.encode=function(a){var b=null;if(a!=null&&a.constructor!=null){var c=mxCodecRegistry.getCodec(a.constructor);c!=null?b=c.encode(this,a):mxUtils.isNode(a)?b=mxClient.IS_IE?a.cloneNode(true):this.document.importNode(a,true):mxLog.warn("mxCodec.encode: No codec for "+mxUtils.getFunctionName(a.constructor))}return b};
-mxCodec.prototype.decode=function(a,b){var c=null;if(a!=null&&a.nodeType==mxConstants.NODETYPE_ELEMENT){var d=null;try{d=eval(a.nodeName)}catch(e){}try{var f=mxCodecRegistry.getCodec(d);if(f!=null)c=f.decode(this,a,b);else{c=a.cloneNode(true);c.removeAttribute("as")}}catch(g){mxLog.debug("Cannot decode "+a.nodeName+": "+g.message)}}return c};
-mxCodec.prototype.encodeCell=function(a,b,c){b.appendChild(this.encode(a));if(c==null||c)for(var c=a.getChildCount(),d=0;d<c;d++)this.encodeCell(a.getChildAt(d),b)};mxCodec.prototype.isCellCodec=function(a){return a!=null&&typeof a.isCellCodec=="function"?a.isCellCodec():false};
-mxCodec.prototype.decodeCell=function(a,b){var b=b!=null?b:true,c=null;if(a!=null&&a.nodeType==mxConstants.NODETYPE_ELEMENT){c=mxCodecRegistry.getCodec(a.nodeName);if(!this.isCellCodec(c))for(var d=a.firstChild;d!=null&&!this.isCellCodec(c);){c=mxCodecRegistry.getCodec(d.nodeName);d=d.nextSibling}this.isCellCodec(c)||(c=mxCodecRegistry.getCodec(mxCell));c=c.decode(this,a);b&&this.insertIntoGraph(c)}return c};
-mxCodec.prototype.insertIntoGraph=function(a){var b=a.parent,c=a.getTerminal(true),d=a.getTerminal(false);a.setTerminal(null,false);a.setTerminal(null,true);a.parent=null;b!=null&&b.insert(a);c!=null&&c.insertEdge(a,true);d!=null&&d.insertEdge(a,false)};mxCodec.prototype.setAttribute=function(a,b,c){b!=null&&c!=null&&a.setAttribute(b,c)};
-function mxObjectCodec(a,b,c,d){this.template=a;this.exclude=b!=null?b:[];this.idrefs=c!=null?c:[];this.mapping=d!=null?d:[];this.reverse={};for(var e in this.mapping)this.reverse[this.mapping[e]]=e}mxObjectCodec.prototype.template=null;mxObjectCodec.prototype.exclude=null;mxObjectCodec.prototype.idrefs=null;mxObjectCodec.prototype.mapping=null;mxObjectCodec.prototype.reverse=null;mxObjectCodec.prototype.getName=function(){return mxUtils.getFunctionName(this.template.constructor)};
-mxObjectCodec.prototype.cloneTemplate=function(){return new this.template.constructor};mxObjectCodec.prototype.getFieldName=function(a){if(a!=null){var b=this.reverse[a];b!=null&&(a=b)}return a};mxObjectCodec.prototype.getAttributeName=function(a){if(a!=null){var b=this.mapping[a];b!=null&&(a=b)}return a};mxObjectCodec.prototype.isExcluded=function(a,b){return b==mxObjectIdentity.FIELD_NAME||mxUtils.indexOf(this.exclude,b)>=0};
-mxObjectCodec.prototype.isReference=function(a,b){return mxUtils.indexOf(this.idrefs,b)>=0};mxObjectCodec.prototype.encode=function(a,b){var c=a.document.createElement(this.getName()),b=this.beforeEncode(a,b,c);this.encodeObject(a,b,c);return this.afterEncode(a,b,c)};mxObjectCodec.prototype.encodeObject=function(a,b,c){a.setAttribute(c,"id",a.getId(b));for(var d in b){var e=d,f=b[e];if(f!=null&&!this.isExcluded(b,e,f,true)){mxUtils.isNumeric(e)&&(e=null);this.encodeValue(a,b,e,f,c)}}};
-mxObjectCodec.prototype.encodeValue=function(a,b,c,d,e){if(d!=null){if(this.isReference(b,c,d,true)){var f=a.getId(d);if(f==null){mxLog.warn("mxObjectCodec.encode: No ID for "+this.getName()+"."+c+"="+d);return}d=f}f=this.template[c];if(c==null||a.encodeDefaults||f!=d){c=this.getAttributeName(c);this.writeAttribute(a,b,c,d,e)}}};mxObjectCodec.prototype.writeAttribute=function(a,b,c,d,e){typeof d!="object"?this.writePrimitiveAttribute(a,b,c,d,e):this.writeComplexAttribute(a,b,c,d,e)};
-mxObjectCodec.prototype.writePrimitiveAttribute=function(a,b,c,d,e){d=this.convertValueToXml(d);if(c==null){b=a.document.createElement("add");typeof d=="function"?b.appendChild(a.document.createTextNode(d)):a.setAttribute(b,"value",d);e.appendChild(b)}else typeof d!="function"&&a.setAttribute(e,c,d)};
-mxObjectCodec.prototype.writeComplexAttribute=function(a,b,c,d,e){a=a.encode(d);if(a!=null){c!=null&&a.setAttribute("as",c);e.appendChild(a)}else mxLog.warn("mxObjectCodec.encode: No node for "+this.getName()+"."+c+": "+d)};mxObjectCodec.prototype.convertValueToXml=function(a){if(typeof a.length=="undefined"&&(a==true||a==false))a=a==true?"1":"0";return a};mxObjectCodec.prototype.convertValueFromXml=function(a){mxUtils.isNumeric(a)&&(a=parseFloat(a));return a};
-mxObjectCodec.prototype.beforeEncode=function(a,b){return b};mxObjectCodec.prototype.afterEncode=function(a,b,c){return c};mxObjectCodec.prototype.decode=function(a,b,c){var d=b.getAttribute("id"),e=a.objects[d];if(e==null){e=c||this.cloneTemplate();d!=null&&a.putObject(d,e)}b=this.beforeDecode(a,b,e);this.decodeNode(a,b,e);return this.afterDecode(a,b,e)};mxObjectCodec.prototype.decodeNode=function(a,b,c){if(b!=null){this.decodeAttributes(a,b,c);this.decodeChildren(a,b,c)}};
-mxObjectCodec.prototype.decodeAttributes=function(a,b,c){b=b.attributes;if(b!=null)for(var d=0;d<b.length;d++)this.decodeAttribute(a,b[d],c)};mxObjectCodec.prototype.decodeAttribute=function(a,b,c){var d=b.nodeName;if(d!="as"&&d!="id"){var b=this.convertValueFromXml(b.nodeValue),e=this.getFieldName(d);if(this.isReference(c,e,b,false)){a=a.getObject(b);if(a==null){mxLog.warn("mxObjectCodec.decode: No object for "+this.getName()+"."+d+"="+b);return}b=a}this.isExcluded(c,d,b,false)||(c[d]=b)}};
-mxObjectCodec.prototype.decodeChildren=function(a,b,c){for(b=b.firstChild;b!=null;){var d=b.nextSibling;b.nodeType==mxConstants.NODETYPE_ELEMENT&&!this.processInclude(a,b,c)&&this.decodeChild(a,b,c);b=d}};
-mxObjectCodec.prototype.decodeChild=function(a,b,c){var d=this.getFieldName(b.getAttribute("as"));if(d==null||!this.isExcluded(c,d,b,false)){var e=this.getFieldTemplate(c,d,b),f=null;if(b.nodeName=="add"){f=b.getAttribute("value");f==null&&(f=mxUtils.eval(mxUtils.getTextContent(b)))}else f=a.decode(b,e);this.addObjectValue(c,d,f,e)}};mxObjectCodec.prototype.getFieldTemplate=function(a,b){var c=a[b];c instanceof Array&&c.length>0&&(c=null);return c};
-mxObjectCodec.prototype.addObjectValue=function(a,b,c,d){c!=null&&c!=d&&(b!=null&&b.length>0?a[b]=c:a.push(c))};mxObjectCodec.prototype.processInclude=function(a,b,c){if(b.nodeName=="include"){b=b.getAttribute("name");if(b!=null)try{var d=mxUtils.load(b).getDocumentElement();d!=null&&a.decode(d,c)}catch(e){}return true}return false};mxObjectCodec.prototype.beforeDecode=function(a,b){return b};mxObjectCodec.prototype.afterDecode=function(a,b,c){return c};
-mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxCell,["children","edges","overlays","mxTransient"],["parent","source","target"]);a.isCellCodec=function(){return true};a.isExcluded=function(a,c,d,e){return mxObjectCodec.prototype.isExcluded.apply(this,arguments)||e&&c=="value"&&d.nodeType==mxConstants.NODETYPE_ELEMENT};a.afterEncode=function(a,c,d){if(c.value!=null&&c.value.nodeType==mxConstants.NODETYPE_ELEMENT){var e=d,d=mxClient.IS_IE?c.value.cloneNode(true):a.document.importNode(c.value,
-true);d.appendChild(e);a=e.getAttribute("id");d.setAttribute("id",a);e.removeAttribute("id")}return d};a.beforeDecode=function(a,c,d){var e=c,f=this.getName();if(c.nodeName!=f){e=c.getElementsByTagName(f)[0];if(e!=null&&e.parentNode==c){mxUtils.removeWhitespace(e,true);mxUtils.removeWhitespace(e,false);e.parentNode.removeChild(e)}else e=null;d.value=c.cloneNode(true);c=d.value.getAttribute("id");if(c!=null){d.setId(c);d.value.removeAttribute("id")}}else d.setId(c.getAttribute("id"));if(e!=null)for(c=
-0;c<this.idrefs.length;c++){var f=this.idrefs[c],g=e.getAttribute(f);if(g!=null){e.removeAttribute(f);var h=a.objects[g]||a.lookup(g);if(h==null){g=a.getElementById(g);g!=null&&(h=(mxCodecRegistry.codecs[g.nodeName]||this).decode(a,g))}d[f]=h}}return e};return a}());
-mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxGraphModel);a.encodeObject=function(a,c,d){var e=a.document.createElement("root");a.encodeCell(c.getRoot(),e);d.appendChild(e)};a.decodeChild=function(a,c,d){c.nodeName=="root"?this.decodeRoot(a,c,d):mxObjectCodec.prototype.decodeChild.apply(this,arguments)};a.decodeRoot=function(a,c,d){for(var e=null,c=c.firstChild;c!=null;){var f=a.decodeCell(c);f!=null&&f.getParent()==null&&(e=f);c=c.nextSibling}e!=null&&d.setRoot(e)};return a}());
-mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxRootChange,["model","previous","root"]);a.afterEncode=function(a,c,d){a.encodeCell(c.root,d);return d};a.beforeDecode=function(a,c,d){if(c.firstChild!=null&&c.firstChild.nodeType==mxConstants.NODETYPE_ELEMENT){var c=c.cloneNode(true),e=c.firstChild;d.root=a.decodeCell(e,false);d=e.nextSibling;e.parentNode.removeChild(e);for(e=d;e!=null;){d=e.nextSibling;a.decodeCell(e);e.parentNode.removeChild(e);e=d}}return c};a.afterDecode=function(a,
-c,d){d.previous=d.root;return d};return a}());
-mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxChildChange,["model","child","previousIndex"],["parent","previous"]);a.isReference=function(a,c,d,e){return c=="child"&&(a.previous!=null||!e)?true:mxUtils.indexOf(this.idrefs,c)>=0};a.afterEncode=function(a,c,d){this.isReference(c,"child",c.child,true)?d.setAttribute("child",a.getId(c.child)):a.encodeCell(c.child,d);return d};a.beforeDecode=function(a,c,d){if(c.firstChild!=null&&c.firstChild.nodeType==mxConstants.NODETYPE_ELEMENT){var c=
-c.cloneNode(true),e=c.firstChild;d.child=a.decodeCell(e,false);d=e.nextSibling;e.parentNode.removeChild(e);for(e=d;e!=null;){d=e.nextSibling;if(e.nodeType==mxConstants.NODETYPE_ELEMENT){var f=e.getAttribute("id");a.lookup(f)==null&&a.decodeCell(e)}e.parentNode.removeChild(e);e=d}}else{e=c.getAttribute("child");d.child=a.getObject(e)}return c};a.afterDecode=function(a,c,d){d.child.parent=d.previous;d.previous=d.parent;d.previousIndex=d.index;return d};return a}());
-mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxTerminalChange,["model","previous"],["cell","terminal"]);a.afterDecode=function(a,c,d){d.previous=d.terminal;return d};return a}());var mxGenericChangeCodec=function(a,b){var c=new mxObjectCodec(a,["model","previous"],["cell"]);c.afterDecode=function(a,c,f){if(mxUtils.isNode(f.cell))f.cell=a.decodeCell(f.cell,false);f.previous=f[b];return f};return c};mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange,"value"));
-mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange,"style"));mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange,"geometry"));mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange,"collapsed"));mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange,"visible"));mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange,"value"));
-mxCodecRegistry.register(function(){return new mxObjectCodec(new mxGraph,["graphListeners","eventListeners","view","container","cellRenderer","editor","selection"])}());
-mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxGraphView);a.encode=function(a,c){return this.encodeCell(a,c,c.graph.getModel().getRoot())};a.encodeCell=function(a,c,d){var e=c.graph.getModel(),f=c.getState(d),g=e.getParent(d);if(g==null||f!=null){var h=e.getChildCount(d),k=c.graph.getCellGeometry(d),i=null;g==e.getRoot()?i="layer":g==null?i="graph":e.isEdge(d)?i="edge":h>0&&k!=null?i="group":e.isVertex(d)&&(i="vertex");if(i!=null){var l=a.document.createElement(i);if(c.graph.getLabel(d)!=
-null){l.setAttribute("label",c.graph.getLabel(d));c.graph.isHtmlLabel(d)&&l.setAttribute("html",true)}if(g==null){var m=c.getGraphBounds();if(m!=null){l.setAttribute("x",Math.round(m.x));l.setAttribute("y",Math.round(m.y));l.setAttribute("width",Math.round(m.width));l.setAttribute("height",Math.round(m.height))}l.setAttribute("scale",c.scale)}else if(f!=null&&k!=null){for(m in f.style){g=f.style[m];typeof g=="function"&&typeof g=="object"&&(g=mxStyleRegistry.getName(g));g!=null&&(typeof g!="function"&&
-typeof g!="object")&&l.setAttribute(m,g)}g=f.absolutePoints;if(g!=null&&g.length>0){k=Math.round(g[0].x)+","+Math.round(g[0].y);for(m=1;m<g.length;m++)k=k+(" "+Math.round(g[m].x)+","+Math.round(g[m].y));l.setAttribute("points",k)}else{l.setAttribute("x",Math.round(f.x));l.setAttribute("y",Math.round(f.y));l.setAttribute("width",Math.round(f.width));l.setAttribute("height",Math.round(f.height))}m=f.absoluteOffset;if(m!=null){m.x!=0&&l.setAttribute("dx",Math.round(m.x));m.y!=0&&l.setAttribute("dy",
-Math.round(m.y))}}for(m=0;m<h;m++){f=this.encodeCell(a,c,e.getChildAt(d,m));f!=null&&l.appendChild(f)}}}return l};return a}());
-mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxStylesheet);a.encode=function(a,c){var d=a.document.createElement(this.getName()),e;for(e in c.styles){var f=c.styles[e],g=a.document.createElement("add");if(e!=null){g.setAttribute("as",e);for(var h in f){var k=this.getStringValue(h,f[h]);if(k!=null){var i=a.document.createElement("add");i.setAttribute("value",k);i.setAttribute("as",h);g.appendChild(i)}}g.childNodes.length>0&&d.appendChild(g)}}return d};a.getStringValue=function(a,
-c){var d=typeof c;d=="function"?c=mxStyleRegistry.getName(style[j]):d=="object"&&(c=null);return c};a.decode=function(a,c,d){var d=d||new this.template.constructor,e=c.getAttribute("id");e!=null&&(a.objects[e]=d);for(c=c.firstChild;c!=null;){if(!this.processInclude(a,c,d)&&c.nodeName=="add"){e=c.getAttribute("as");if(e!=null){var f=c.getAttribute("extend"),g=f!=null?mxUtils.clone(d.styles[f]):null;if(g==null){f!=null&&mxLog.warn("mxStylesheetCodec.decode: stylesheet "+f+" not found to extend");g=
-{}}for(f=c.firstChild;f!=null;){if(f.nodeType==mxConstants.NODETYPE_ELEMENT){var h=f.getAttribute("as");if(f.nodeName=="add"){var k=mxUtils.getTextContent(f),i=null;if(k!=null&&k.length>0)i=mxUtils.eval(k);else{i=f.getAttribute("value");mxUtils.isNumeric(i)&&(i=parseFloat(i))}i!=null&&(g[h]=i)}else f.nodeName=="remove"&&delete g[h]}f=f.nextSibling}d.putCellStyle(e,g)}}c=c.nextSibling}return d};return a}());
-mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxDefaultKeyHandler);a.encode=function(){return null};a.decode=function(a,c,d){if(d!=null)for(c=c.firstChild;c!=null;){if(!this.processInclude(a,c,d)&&c.nodeName=="add"){var e=c.getAttribute("as"),f=c.getAttribute("action"),g=c.getAttribute("control");d.bindAction(e,f,g)}c=c.nextSibling}return d};return a}());
-mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxDefaultToolbar);a.encode=function(){return null};a.decode=function(a,c,d){if(d!=null)for(var e=d.editor,c=c.firstChild;c!=null;){if(c.nodeType==mxConstants.NODETYPE_ELEMENT&&!this.processInclude(a,c,d))if(c.nodeName=="separator")d.addSeparator();else if(c.nodeName=="br")d.toolbar.addBreak();else if(c.nodeName=="hr")d.toolbar.addLine();else if(c.nodeName=="add"){var f=c.getAttribute("as"),f=mxResources.get(f)||f,g=c.getAttribute("icon"),
-h=c.getAttribute("pressedIcon"),k=c.getAttribute("action"),i=c.getAttribute("mode"),l=c.getAttribute("template"),m=c.getAttribute("toggle")!="0",n=mxUtils.getTextContent(c),o=null;if(k!=null)o=d.addItem(f,g,k,h);else if(i!=null)var p=mxUtils.eval(n),o=d.addMode(f,g,i,h,p);else if(l!=null||n!=null&&n.length>0){o=e.templates[l];l=c.getAttribute("style");if(o!=null&&l!=null){o=o.clone();o.setStyle(l)}l=null;n!=null&&n.length>0&&(l=mxUtils.eval(n));o=d.addPrototype(f,g,o,h,l,m)}else{h=mxUtils.getChildNodes(c);
-if(h.length>0)if(g==null){l=d.addActionCombo(f);for(f=0;f<h.length;f++){m=h[f];if(m.nodeName=="separator")d.addOption(l,"---");else if(m.nodeName=="add"){g=m.getAttribute("as");m=m.getAttribute("action");d.addActionOption(l,g,m)}}}else{var q=null,t=d.addPrototype(f,g,function(){var a=e.templates[q.value];if(a!=null){var a=a.clone(),b=q.options[q.selectedIndex].cellStyle;b!=null&&a.setStyle(b);return a}mxLog.warn("Template "+a+" not found");return null},null,null,m),q=d.addCombo();mxEvent.addListener(q,
-"change",function(){d.toolbar.selectMode(t,function(a){a=mxUtils.convertPoint(e.graph.container,mxEvent.getClientX(a),mxEvent.getClientY(a));return e.addVertex(null,p(),a.x,a.y)});d.toolbar.noReset=false});for(f=0;f<h.length;f++){m=h[f];if(m.nodeName=="separator")d.addOption(q,"---");else if(m.nodeName=="add"){g=m.getAttribute("as");n=m.getAttribute("template");d.addOption(q,g,n||l).cellStyle=m.getAttribute("style")}}}}if(o!=null){l=c.getAttribute("id");l!=null&&l.length>0&&o.setAttribute("id",l)}}c=
-c.nextSibling}return d};return a}());mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxDefaultPopupMenu);a.encode=function(){return null};a.decode=function(a,c,d){var e=c.getElementsByTagName("include")[0];if(e!=null)this.processInclude(a,e,d);else if(d!=null)d.config=c;return d};return a}());
-mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxEditor,["modified","lastSnapshot","ignoredChanges","undoManager","graphContainer","toolbarContainer"]);a.afterDecode=function(a,c,d){a=c.getAttribute("defaultEdge");if(a!=null){c.removeAttribute("defaultEdge");d.defaultEdge=d.templates[a]}a=c.getAttribute("defaultGroup");if(a!=null){c.removeAttribute("defaultGroup");d.defaultGroup=d.templates[a]}return d};a.decodeChild=function(a,c,d){if(c.nodeName=="Array"){if(c.getAttribute("as")==
-"templates"){this.decodeTemplates(a,c,d);return}}else if(c.nodeName=="ui"){this.decodeUi(a,c,d);return}mxObjectCodec.prototype.decodeChild.apply(this,arguments)};a.decodeUi=function(a,c,d){for(a=c.firstChild;a!=null;){if(a.nodeName=="add"){var c=a.getAttribute("as"),e=a.getAttribute("element"),f=a.getAttribute("style"),g=null;if(e!=null){g=document.getElementById(e);if(g!=null&&f!=null)g.style.cssText=g.style.cssText+(";"+f)}else{var e=parseInt(a.getAttribute("x")),h=parseInt(a.getAttribute("y")),
-k=a.getAttribute("width"),i=a.getAttribute("height"),g=document.createElement("div");g.style.cssText=f;(new mxWindow(mxResources.get(c)||c,g,e,h,k,i,false,true)).setVisible(true)}c=="graph"?d.setGraphContainer(g):c=="toolbar"?d.setToolbarContainer(g):c=="title"?d.setTitleContainer(g):c=="status"?d.setStatusContainer(g):c=="map"&&d.setMapContainer(g)}else a.nodeName=="resource"?mxResources.add(a.getAttribute("basename")):a.nodeName=="stylesheet"&&mxClient.link("stylesheet",a.getAttribute("name"));
-a=a.nextSibling}};a.decodeTemplates=function(a,c,d){if(d.templates==null)d.templates=[];for(var c=mxUtils.getChildNodes(c),e=0;e<c.length;e++){for(var f=c[e].getAttribute("as"),g=c[e].firstChild;g!=null&&g.nodeType!=1;)g=g.nextSibling;g!=null&&(d.templates[f]=a.decodeCell(g))}};return a}());
diff --git a/css/common.css b/src/css/common.css
index 5eb0b45..5eb0b45 100644
--- a/css/common.css
+++ b/src/css/common.css
diff --git a/css/explorer.css b/src/css/explorer.css
index dfbbd21..dfbbd21 100644
--- a/css/explorer.css
+++ b/src/css/explorer.css
diff --git a/images/button.gif b/src/images/button.gif
index ad55cab..ad55cab 100644
--- a/images/button.gif
+++ b/src/images/button.gif
Binary files differ
diff --git a/images/close.gif b/src/images/close.gif
index 1069e94..1069e94 100644
--- a/images/close.gif
+++ b/src/images/close.gif
Binary files differ
diff --git a/images/collapsed.gif b/src/images/collapsed.gif
index 0276444..0276444 100644
--- a/images/collapsed.gif
+++ b/src/images/collapsed.gif
Binary files differ
diff --git a/images/error.gif b/src/images/error.gif
index 14e1aee..14e1aee 100644
--- a/images/error.gif
+++ b/src/images/error.gif
Binary files differ
diff --git a/images/expanded.gif b/src/images/expanded.gif
index 3767b0b..3767b0b 100644
--- a/images/expanded.gif
+++ b/src/images/expanded.gif
Binary files differ
diff --git a/images/maximize.gif b/src/images/maximize.gif
index e27cf3e..e27cf3e 100644
--- a/images/maximize.gif
+++ b/src/images/maximize.gif
Binary files differ
diff --git a/images/minimize.gif b/src/images/minimize.gif
index 1e95e7c..1e95e7c 100644
--- a/images/minimize.gif
+++ b/src/images/minimize.gif
Binary files differ
diff --git a/images/normalize.gif b/src/images/normalize.gif
index 34a8d30..34a8d30 100644
--- a/images/normalize.gif
+++ b/src/images/normalize.gif
Binary files differ
diff --git a/images/point.gif b/src/images/point.gif
index 9074c39..9074c39 100644
--- a/images/point.gif
+++ b/src/images/point.gif
Binary files differ
diff --git a/images/resize.gif b/src/images/resize.gif
index ff558db..ff558db 100644
--- a/images/resize.gif
+++ b/src/images/resize.gif
Binary files differ
diff --git a/images/separator.gif b/src/images/separator.gif
index 5c1b895..5c1b895 100644
--- a/images/separator.gif
+++ b/src/images/separator.gif
Binary files differ
diff --git a/images/submenu.gif b/src/images/submenu.gif
index ffe7617..ffe7617 100644
--- a/images/submenu.gif
+++ b/src/images/submenu.gif
Binary files differ
diff --git a/images/transparent.gif b/src/images/transparent.gif
index 76040f2..76040f2 100644
--- a/images/transparent.gif
+++ b/src/images/transparent.gif
Binary files differ
diff --git a/images/warning.gif b/src/images/warning.gif
index 705235f..705235f 100644
--- a/images/warning.gif
+++ b/src/images/warning.gif
Binary files differ
diff --git a/images/warning.png b/src/images/warning.png
index 2f78789..2f78789 100644
--- a/images/warning.png
+++ b/src/images/warning.png
Binary files differ
diff --git a/images/window-title.gif b/src/images/window-title.gif
index 231def8..231def8 100644
--- a/images/window-title.gif
+++ b/src/images/window-title.gif
Binary files differ
diff --git a/images/window.gif b/src/images/window.gif
index 6631c4f..6631c4f 100644
--- a/images/window.gif
+++ b/src/images/window.gif
Binary files differ
diff --git a/src/js/editor/mxDefaultKeyHandler.js b/src/js/editor/mxDefaultKeyHandler.js
new file mode 100644
index 0000000..3814e5e
--- /dev/null
+++ b/src/js/editor/mxDefaultKeyHandler.js
@@ -0,0 +1,126 @@
+/**
+ * $Id: mxDefaultKeyHandler.js,v 1.26 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDefaultKeyHandler
+ *
+ * Binds keycodes to actionnames in an editor. This aggregates an internal
+ * <handler> and extends the implementation of <mxKeyHandler.escape> to not
+ * only cancel the editing, but also hide the properties dialog and fire an
+ * <mxEditor.escape> event via <editor>. An instance of this class is created
+ * by <mxEditor> and stored in <mxEditor.keyHandler>.
+ *
+ * Example:
+ *
+ * Bind the delete key to the delete action in an existing editor.
+ *
+ * (code)
+ * var keyHandler = new mxDefaultKeyHandler(editor);
+ * keyHandler.bindAction(46, 'delete');
+ * (end)
+ *
+ * Codec:
+ *
+ * This class uses the <mxDefaultKeyHandlerCodec> to read configuration
+ * data into an existing instance. See <mxDefaultKeyHandlerCodec> for a
+ * description of the configuration format.
+ *
+ * Keycodes:
+ *
+ * See <mxKeyHandler>.
+ *
+ * An <mxEvent.ESCAPE> event is fired via the editor if the escape key is
+ * pressed.
+ *
+ * Constructor: mxDefaultKeyHandler
+ *
+ * Constructs a new default key handler for the <mxEditor.graph> in the
+ * given <mxEditor>. (The editor may be null if a prototypical instance for
+ * a <mxDefaultKeyHandlerCodec> is created.)
+ *
+ * Parameters:
+ *
+ * editor - Reference to the enclosing <mxEditor>.
+ */
+function mxDefaultKeyHandler(editor)
+{
+ if (editor != null)
+ {
+ this.editor = editor;
+ this.handler = new mxKeyHandler(editor.graph);
+
+ // Extends the escape function of the internal key
+ // handle to hide the properties dialog and fire
+ // the escape event via the editor instance
+ var old = this.handler.escape;
+
+ this.handler.escape = function(evt)
+ {
+ old.apply(this, arguments);
+ editor.hideProperties();
+ editor.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));
+ };
+ }
+};
+
+/**
+ * Variable: editor
+ *
+ * Reference to the enclosing <mxEditor>.
+ */
+mxDefaultKeyHandler.prototype.editor = null;
+
+/**
+ * Variable: handler
+ *
+ * Holds the <mxKeyHandler> for key event handling.
+ */
+mxDefaultKeyHandler.prototype.handler = null;
+
+/**
+ * Function: bindAction
+ *
+ * Binds the specified keycode to the given action in <editor>. The
+ * optional control flag specifies if the control key must be pressed
+ * to trigger the action.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * action - Name of the action to execute in <editor>.
+ * control - Optional boolean that specifies if control must be pressed.
+ * Default is false.
+ */
+mxDefaultKeyHandler.prototype.bindAction = function (code, action, control)
+{
+ var keyHandler = mxUtils.bind(this, function()
+ {
+ this.editor.execute(action);
+ });
+
+ // Binds the function to control-down keycode
+ if (control)
+ {
+ this.handler.bindControlKey(code, keyHandler);
+ }
+
+ // Binds the function to the normal keycode
+ else
+ {
+ this.handler.bindKey(code, keyHandler);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the <handler> associated with this object. This does normally
+ * not need to be called, the <handler> is destroyed automatically when the
+ * window unloads (in IE) by <mxEditor>.
+ */
+mxDefaultKeyHandler.prototype.destroy = function ()
+{
+ this.handler.destroy();
+ this.handler = null;
+};
diff --git a/src/js/editor/mxDefaultPopupMenu.js b/src/js/editor/mxDefaultPopupMenu.js
new file mode 100644
index 0000000..01c65b5
--- /dev/null
+++ b/src/js/editor/mxDefaultPopupMenu.js
@@ -0,0 +1,300 @@
+/**
+ * $Id: mxDefaultPopupMenu.js,v 1.29 2012-07-03 06:30:25 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDefaultPopupMenu
+ *
+ * Creates popupmenus for mouse events. This object holds an XML node
+ * which is a description of the popup menu to be created. In
+ * <createMenu>, the configuration is applied to the context and
+ * the resulting menu items are added to the menu dynamically. See
+ * <createMenu> for a description of the configuration format.
+ *
+ * This class does not create the DOM nodes required for the popup menu, it
+ * only parses an XML description to invoke the respective methods on an
+ * <mxPopupMenu> each time the menu is displayed.
+ *
+ * Codec:
+ *
+ * This class uses the <mxDefaultPopupMenuCodec> to read configuration
+ * data into an existing instance, however, the actual parsing is done
+ * by this class during program execution, so the format is described
+ * below.
+ *
+ * Constructor: mxDefaultPopupMenu
+ *
+ * Constructs a new popupmenu-factory based on given configuration.
+ *
+ * Paramaters:
+ *
+ * config - XML node that contains the configuration data.
+ */
+function mxDefaultPopupMenu(config)
+{
+ this.config = config;
+};
+
+/**
+ * Variable: imageBasePath
+ *
+ * Base path for all icon attributes in the config. Default is null.
+ */
+mxDefaultPopupMenu.prototype.imageBasePath = null;
+
+/**
+ * Variable: config
+ *
+ * XML node used as the description of new menu items. This node is
+ * used in <createMenu> to dynamically create the menu items if their
+ * respective conditions evaluate to true for the given arguments.
+ */
+mxDefaultPopupMenu.prototype.config = null;
+
+/**
+ * Function: createMenu
+ *
+ * This function is called from <mxEditor> to add items to the
+ * given menu based on <config>. The config is a sequence of
+ * the following nodes and attributes.
+ *
+ * Child Nodes:
+ *
+ * add - Adds a new menu item. See below for attributes.
+ * separator - Adds a separator. No attributes.
+ * condition - Adds a custom condition. Name attribute.
+ *
+ * The add-node may have a child node that defines a function to be invoked
+ * before the action is executed (or instead of an action to be executed).
+ *
+ * Attributes:
+ *
+ * as - Resource key for the label (needs entry in property file).
+ * action - Name of the action to execute in enclosing editor.
+ * icon - Optional icon (relative/absolute URL).
+ * iconCls - Optional CSS class for the icon.
+ * if - Optional name of condition that must be true(see below).
+ * name - Name of custom condition. Only for condition nodes.
+ *
+ * Conditions:
+ *
+ * nocell - No cell under the mouse.
+ * ncells - More than one cell selected.
+ * notRoot - Drilling position is other than home.
+ * cell - Cell under the mouse.
+ * notEmpty - Exactly one cell with children under mouse.
+ * expandable - Exactly one expandable cell under mouse.
+ * collapsable - Exactly one collapsable cell under mouse.
+ * validRoot - Exactly one cell which is a possible root under mouse.
+ * swimlane - Exactly one cell which is a swimlane under mouse.
+ *
+ * Example:
+ *
+ * To add a new item for a given action to the popupmenu:
+ *
+ * (code)
+ * <mxDefaultPopupMenu as="popupHandler">
+ * <add as="delete" action="delete" icon="images/delete.gif" if="cell"/>
+ * </mxDefaultPopupMenu>
+ * (end)
+ *
+ * To add a new item for a custom function:
+ *
+ * (code)
+ * <mxDefaultPopupMenu as="popupHandler">
+ * <add as="action1"><![CDATA[
+ * function (editor, cell, evt)
+ * {
+ * editor.execute('action1', cell, 'myArg');
+ * }
+ * ]]></add>
+ * </mxDefaultPopupMenu>
+ * (end)
+ *
+ * The above example invokes action1 with an additional third argument via
+ * the editor instance. The third argument is passed to the function that
+ * defines action1. If the add-node has no action-attribute, then only the
+ * function defined in the text content is executed, otherwise first the
+ * function and then the action defined in the action-attribute is
+ * executed. The function in the text content has 3 arguments, namely the
+ * <mxEditor> instance, the <mxCell> instance under the mouse, and the
+ * native mouse event.
+ *
+ * Custom Conditions:
+ *
+ * To add a new condition for popupmenu items:
+ *
+ * (code)
+ * <condition name="condition1"><![CDATA[
+ * function (editor, cell, evt)
+ * {
+ * return cell != null;
+ * }
+ * ]]></condition>
+ * (end)
+ *
+ * The new condition can then be used in any item as follows:
+ *
+ * (code)
+ * <add as="action1" action="action1" icon="action1.gif" if="condition1"/>
+ * (end)
+ *
+ * The order in which the items and conditions appear is not significant as
+ * all connditions are evaluated before any items are created.
+ *
+ * Parameters:
+ *
+ * editor - Enclosing <mxEditor> instance.
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * cell - Optional <mxCell> which is under the mousepointer.
+ * evt - Optional mouse event which triggered the menu.
+ */
+mxDefaultPopupMenu.prototype.createMenu = function(editor, menu, cell, evt)
+{
+ if (this.config != null)
+ {
+ var conditions = this.createConditions(editor, cell, evt);
+ var item = this.config.firstChild;
+
+ this.addItems(editor, menu, cell, evt, conditions, item, null);
+ }
+};
+
+/**
+ * Function: addItems
+ *
+ * Recursively adds the given items and all of its children into the given menu.
+ *
+ * Parameters:
+ *
+ * editor - Enclosing <mxEditor> instance.
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * cell - Optional <mxCell> which is under the mousepointer.
+ * evt - Optional mouse event which triggered the menu.
+ * conditions - Array of names boolean conditions.
+ * item - XML node that represents the current menu item.
+ * parent - DOM node that represents the parent menu item.
+ */
+mxDefaultPopupMenu.prototype.addItems = function(editor, menu, cell, evt, conditions, item, parent)
+{
+ var addSeparator = false;
+
+ while (item != null)
+ {
+ if (item.nodeName == 'add')
+ {
+ var condition = item.getAttribute('if');
+
+ if (condition == null || conditions[condition])
+ {
+ var as = item.getAttribute('as');
+ as = mxResources.get(as) || as;
+ var funct = mxUtils.eval(mxUtils.getTextContent(item));
+ var action = item.getAttribute('action');
+ var icon = item.getAttribute('icon');
+ var iconCls = item.getAttribute('iconCls');
+
+ if (addSeparator)
+ {
+ menu.addSeparator(parent);
+ addSeparator = false;
+ }
+
+ if (icon != null && this.imageBasePath)
+ {
+ icon = this.imageBasePath + icon;
+ }
+
+ var row = this.addAction(menu, editor, as, icon, funct, action, cell, parent, iconCls);
+ this.addItems(editor, menu, cell, evt, conditions, item.firstChild, row);
+ }
+ }
+ else if (item.nodeName == 'separator')
+ {
+ addSeparator = true;
+ }
+
+ item = item.nextSibling;
+ }
+};
+
+/**
+ * Function: addAction
+ *
+ * Helper method to bind an action to a new menu item.
+ *
+ * Parameters:
+ *
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * editor - Enclosing <mxEditor> instance.
+ * lab - String that represents the label of the menu item.
+ * icon - Optional URL that represents the icon of the menu item.
+ * action - Optional name of the action to execute in the given editor.
+ * funct - Optional function to execute before the optional action. The
+ * function takes an <mxEditor>, the <mxCell> under the mouse and the
+ * mouse event that triggered the call.
+ * cell - Optional <mxCell> to use as an argument for the action.
+ * parent - DOM node that represents the parent menu item.
+ * iconCls - Optional CSS class for the menu icon.
+ */
+mxDefaultPopupMenu.prototype.addAction = function(menu, editor, lab, icon, funct, action, cell, parent, iconCls)
+{
+ var clickHandler = function(evt)
+ {
+ if (typeof(funct) == 'function')
+ {
+ funct.call(editor, editor, cell, evt);
+ }
+
+ if (action != null)
+ {
+ editor.execute(action, cell, evt);
+ }
+ };
+
+ return menu.addItem(lab, icon, clickHandler, parent, iconCls);
+};
+
+/**
+ * Function: createConditions
+ *
+ * Evaluates the default conditions for the given context.
+ */
+mxDefaultPopupMenu.prototype.createConditions = function(editor, cell, evt)
+{
+ // Creates array with conditions
+ var model = editor.graph.getModel();
+ var childCount = model.getChildCount(cell);
+
+ // Adds some frequently used conditions
+ var conditions = [];
+ conditions['nocell'] = cell == null;
+ conditions['ncells'] = editor.graph.getSelectionCount() > 1;
+ conditions['notRoot'] = model.getRoot() !=
+ model.getParent(editor.graph.getDefaultParent());
+ conditions['cell'] = cell != null;
+
+ var isCell = cell != null && editor.graph.getSelectionCount() == 1;
+ conditions['nonEmpty'] = isCell && childCount > 0;
+ conditions['expandable'] = isCell && editor.graph.isCellFoldable(cell, false);
+ conditions['collapsable'] = isCell && editor.graph.isCellFoldable(cell, true);
+ conditions['validRoot'] = isCell && editor.graph.isValidRoot(cell);
+ conditions['emptyValidRoot'] = conditions['validRoot'] && childCount == 0;
+ conditions['swimlane'] = isCell && editor.graph.isSwimlane(cell);
+
+ // Evaluates dynamic conditions from config file
+ var condNodes = this.config.getElementsByTagName('condition');
+
+ for (var i=0; i<condNodes.length; i++)
+ {
+ var funct = mxUtils.eval(mxUtils.getTextContent(condNodes[i]));
+ var name = condNodes[i].getAttribute('name');
+
+ if (name != null && typeof(funct) == 'function')
+ {
+ conditions[name] = funct(editor, cell, evt);
+ }
+ }
+
+ return conditions;
+};
diff --git a/src/js/editor/mxDefaultToolbar.js b/src/js/editor/mxDefaultToolbar.js
new file mode 100644
index 0000000..3f8f901
--- /dev/null
+++ b/src/js/editor/mxDefaultToolbar.js
@@ -0,0 +1,567 @@
+/**
+ * $Id: mxDefaultToolbar.js,v 1.67 2012-04-11 07:00:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDefaultToolbar
+ *
+ * Toolbar for the editor. This modifies the state of the graph
+ * or inserts new cells upon mouse clicks.
+ *
+ * Example:
+ *
+ * Create a toolbar with a button to copy the selection into the clipboard,
+ * and a combo box with one action to paste the selection from the clipboard
+ * into the graph.
+ *
+ * (code)
+ * var toolbar = new mxDefaultToolbar(container, editor);
+ * toolbar.addItem('Copy', null, 'copy');
+ *
+ * var combo = toolbar.addActionCombo('More actions...');
+ * toolbar.addActionOption(combo, 'Paste', 'paste');
+ * (end)
+ *
+ * Codec:
+ *
+ * This class uses the <mxDefaultToolbarCodec> to read configuration
+ * data into an existing instance. See <mxDefaultToolbarCodec> for a
+ * description of the configuration format.
+ *
+ * Constructor: mxDefaultToolbar
+ *
+ * Constructs a new toolbar for the given container and editor. The
+ * container and editor may be null if a prototypical instance for a
+ * <mxDefaultKeyHandlerCodec> is created.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ * editor - Reference to the enclosing <mxEditor>.
+ */
+function mxDefaultToolbar(container, editor)
+{
+ this.editor = editor;
+
+ if (container != null &&
+ editor != null)
+ {
+ this.init(container);
+ }
+};
+
+/**
+ * Variable: editor
+ *
+ * Reference to the enclosing <mxEditor>.
+ */
+mxDefaultToolbar.prototype.editor = null;
+
+/**
+ * Variable: toolbar
+ *
+ * Holds the internal <mxToolbar>.
+ */
+mxDefaultToolbar.prototype.toolbar = null;
+
+/**
+ * Variable: resetHandler
+ *
+ * Reference to the function used to reset the <toolbar>.
+ */
+mxDefaultToolbar.prototype.resetHandler = null;
+
+/**
+ * Variable: spacing
+ *
+ * Defines the spacing between existing and new vertices in
+ * gridSize units when a new vertex is dropped on an existing
+ * cell. Default is 4 (40 pixels).
+ */
+mxDefaultToolbar.prototype.spacing = 4;
+
+/**
+ * Variable: connectOnDrop
+ *
+ * Specifies if elements should be connected if new cells are dropped onto
+ * connectable elements. Default is false.
+ */
+mxDefaultToolbar.prototype.connectOnDrop = false;
+
+/**
+ * Variable: init
+ *
+ * Constructs the <toolbar> for the given container and installs a listener
+ * that updates the <mxEditor.insertFunction> on <editor> if an item is
+ * selected in the toolbar. This assumes that <editor> is not null.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+mxDefaultToolbar.prototype.init = function(container)
+{
+ if (container != null)
+ {
+ this.toolbar = new mxToolbar(container);
+
+ // Installs the insert function in the editor if an item is
+ // selected in the toolbar
+ this.toolbar.addListener(mxEvent.SELECT,
+ mxUtils.bind(this, function(sender, evt)
+ {
+ var funct = evt.getProperty('function');
+
+ if (funct != null)
+ {
+ this.editor.insertFunction = mxUtils.bind(this, function()
+ {
+ funct.apply(this, arguments);
+ this.toolbar.resetMode();
+ });
+ }
+ else
+ {
+ this.editor.insertFunction = null;
+ }
+ })
+ );
+
+ // Resets the selected tool after a doubleclick or escape keystroke
+ this.resetHandler = mxUtils.bind(this, function()
+ {
+ if (this.toolbar != null)
+ {
+ this.toolbar.resetMode(true);
+ }
+ });
+
+ this.editor.graph.addListener(mxEvent.DOUBLE_CLICK, this.resetHandler);
+ this.editor.addListener(mxEvent.ESCAPE, this.resetHandler);
+ }
+};
+
+/**
+ * Function: addItem
+ *
+ * Adds a new item that executes the given action in <editor>. The title,
+ * icon and pressedIcon are used to display the toolbar item.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title (tooltip) for the item.
+ * icon - URL of the icon to be used for displaying the item.
+ * action - Name of the action to execute when the item is clicked.
+ * pressed - Optional URL of the icon for the pressed state.
+ */
+mxDefaultToolbar.prototype.addItem = function(title, icon, action, pressed)
+{
+ var clickHandler = mxUtils.bind(this, function()
+ {
+ if (action != null && action.length > 0)
+ {
+ this.editor.execute(action);
+ }
+ });
+
+ return this.toolbar.addItem(title, icon, clickHandler, pressed);
+};
+
+/**
+ * Function: addSeparator
+ *
+ * Adds a vertical separator using the optional icon.
+ *
+ * Parameters:
+ *
+ * icon - Optional URL of the icon that represents the vertical separator.
+ * Default is <mxClient.imageBasePath> + '/separator.gif'.
+ */
+mxDefaultToolbar.prototype.addSeparator = function(icon)
+{
+ icon = icon || mxClient.imageBasePath + '/separator.gif';
+ this.toolbar.addSeparator(icon);
+};
+
+/**
+ * Function: addCombo
+ *
+ * Helper method to invoke <mxToolbar.addCombo> on <toolbar> and return the
+ * resulting DOM node.
+ */
+mxDefaultToolbar.prototype.addCombo = function()
+{
+ return this.toolbar.addCombo();
+};
+
+/**
+ * Function: addActionCombo
+ *
+ * Helper method to invoke <mxToolbar.addActionCombo> on <toolbar> using
+ * the given title and return the resulting DOM node.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title of the combo.
+ */
+mxDefaultToolbar.prototype.addActionCombo = function(title)
+{
+ return this.toolbar.addActionCombo(title);
+};
+
+/**
+ * Function: addActionOption
+ *
+ * Binds the given action to a option with the specified label in the
+ * given combo. Combo is an object returned from an earlier call to
+ * <addCombo> or <addActionCombo>.
+ *
+ * Parameters:
+ *
+ * combo - DOM node that represents the combo box.
+ * title - String that represents the title of the combo.
+ * action - Name of the action to execute in <editor>.
+ */
+mxDefaultToolbar.prototype.addActionOption = function(combo, title, action)
+{
+ var clickHandler = mxUtils.bind(this, function()
+ {
+ this.editor.execute(action);
+ });
+
+ this.addOption(combo, title, clickHandler);
+};
+
+/**
+ * Function: addOption
+ *
+ * Helper method to invoke <mxToolbar.addOption> on <toolbar> and return
+ * the resulting DOM node that represents the option.
+ *
+ * Parameters:
+ *
+ * combo - DOM node that represents the combo box.
+ * title - String that represents the title of the combo.
+ * value - Object that represents the value of the option.
+ */
+mxDefaultToolbar.prototype.addOption = function(combo, title, value)
+{
+ return this.toolbar.addOption(combo, title, value);
+};
+
+/**
+ * Function: addMode
+ *
+ * Creates an item for selecting the given mode in the <editor>'s graph.
+ * Supported modenames are select, connect and pan.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title of the item.
+ * icon - URL of the icon that represents the item.
+ * mode - String that represents the mode name to be used in
+ * <mxEditor.setMode>.
+ * pressed - Optional URL of the icon that represents the pressed state.
+ * funct - Optional JavaScript function that takes the <mxEditor> as the
+ * first and only argument that is executed after the mode has been
+ * selected.
+ */
+mxDefaultToolbar.prototype.addMode = function(title, icon, mode, pressed, funct)
+{
+ var clickHandler = mxUtils.bind(this, function()
+ {
+ this.editor.setMode(mode);
+
+ if (funct != null)
+ {
+ funct(this.editor);
+ }
+ });
+
+ return this.toolbar.addSwitchMode(title, icon, clickHandler, pressed);
+};
+
+/**
+ * Function: addPrototype
+ *
+ * Creates an item for inserting a clone of the specified prototype cell into
+ * the <editor>'s graph. The ptype may either be a cell or a function that
+ * returns a cell.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title of the item.
+ * icon - URL of the icon that represents the item.
+ * ptype - Function or object that represents the prototype cell. If ptype
+ * is a function then it is invoked with no arguments to create new
+ * instances.
+ * pressed - Optional URL of the icon that represents the pressed state.
+ * insert - Optional JavaScript function that handles an insert of the new
+ * cell. This function takes the <mxEditor>, new cell to be inserted, mouse
+ * event and optional <mxCell> under the mouse pointer as arguments.
+ * toggle - Optional boolean that specifies if the item can be toggled.
+ * Default is true.
+ */
+mxDefaultToolbar.prototype.addPrototype = function(title, icon, ptype, pressed, insert, toggle)
+{
+ // Creates a wrapper function that is in charge of constructing
+ // the new cell instance to be inserted into the graph
+ var factory = function()
+ {
+ if (typeof(ptype) == 'function')
+ {
+ return ptype();
+ }
+ else if (ptype != null)
+ {
+ return ptype.clone();
+ }
+
+ return null;
+ };
+
+ // Defines the function for a click event on the graph
+ // after this item has been selected in the toolbar
+ var clickHandler = mxUtils.bind(this, function(evt, cell)
+ {
+ if (typeof(insert) == 'function')
+ {
+ insert(this.editor, factory(), evt, cell);
+ }
+ else
+ {
+ this.drop(factory(), evt, cell);
+ }
+
+ this.toolbar.resetMode();
+ mxEvent.consume(evt);
+ });
+
+ var img = this.toolbar.addMode(title, icon, clickHandler, pressed, null, toggle);
+
+ // Creates a wrapper function that calls the click handler without
+ // the graph argument
+ var dropHandler = function(graph, evt, cell)
+ {
+ clickHandler(evt, cell);
+ };
+
+ this.installDropHandler(img, dropHandler);
+
+ return img;
+};
+
+/**
+ * Function: drop
+ *
+ * Handles a drop from a toolbar item to the graph. The given vertex
+ * represents the new cell to be inserted. This invokes <insert> or
+ * <connect> depending on the given target cell.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * target - Optional <mxCell> that represents the drop target.
+ */
+mxDefaultToolbar.prototype.drop = function(vertex, evt, target)
+{
+ var graph = this.editor.graph;
+ var model = graph.getModel();
+
+ if (target == null ||
+ model.isEdge(target) ||
+ !this.connectOnDrop ||
+ !graph.isCellConnectable(target))
+ {
+ while (target != null &&
+ !graph.isValidDropTarget(target, [vertex], evt))
+ {
+ target = model.getParent(target);
+ }
+
+ this.insert(vertex, evt, target);
+ }
+ else
+ {
+ this.connect(vertex, evt, target);
+ }
+};
+
+/**
+ * Function: insert
+ *
+ * Handles a drop by inserting the given vertex into the given parent cell
+ * or the default parent if no parent is specified.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * parent - Optional <mxCell> that represents the parent.
+ */
+mxDefaultToolbar.prototype.insert = function(vertex, evt, target)
+{
+ var graph = this.editor.graph;
+
+ if (graph.canImportCell(vertex))
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+ var pt = mxUtils.convertPoint(graph.container, x, y);
+
+ // Splits the target edge or inserts into target group
+ if (graph.isSplitEnabled() &&
+ graph.isSplitTarget(target, [vertex], evt))
+ {
+ return graph.splitEdge(target, [vertex], null, pt.x, pt.y);
+ }
+ else
+ {
+ return this.editor.addVertex(target, vertex, pt.x, pt.y);
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: connect
+ *
+ * Handles a drop by connecting the given vertex to the given source cell.
+ *
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * source - Optional <mxCell> that represents the source terminal.
+ */
+mxDefaultToolbar.prototype.connect = function(vertex, evt, source)
+{
+ var graph = this.editor.graph;
+ var model = graph.getModel();
+
+ if (source != null &&
+ graph.isCellConnectable(vertex) &&
+ graph.isEdgeValid(null, source, vertex))
+ {
+ var edge = null;
+
+ model.beginUpdate();
+ try
+ {
+ var geo = model.getGeometry(source);
+ var g = model.getGeometry(vertex).clone();
+
+ // Moves the vertex away from the drop target that will
+ // be used as the source for the new connection
+ g.x = geo.x + (geo.width - g.width) / 2;
+ g.y = geo.y + (geo.height - g.height) / 2;
+
+ var step = this.spacing * graph.gridSize;
+ var dist = model.getDirectedEdgeCount(source, true) * 20;
+
+ if (this.editor.horizontalFlow)
+ {
+ g.x += (g.width + geo.width) / 2 + step + dist;
+ }
+ else
+ {
+ g.y += (g.height + geo.height) / 2 + step + dist;
+ }
+
+ vertex.setGeometry(g);
+
+ // Fires two add-events with the code below - should be fixed
+ // to only fire one add event for both inserts
+ var parent = model.getParent(source);
+ graph.addCell(vertex, parent);
+ graph.constrainChild(vertex);
+
+ // Creates the edge using the editor instance and calls
+ // the second function that fires an add event
+ edge = this.editor.createEdge(source, vertex);
+
+ if (model.getGeometry(edge) == null)
+ {
+ var edgeGeometry = new mxGeometry();
+ edgeGeometry.relative = true;
+
+ model.setGeometry(edge, edgeGeometry);
+ }
+
+ graph.addEdge(edge, parent, source, vertex);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+
+ graph.setSelectionCells([vertex, edge]);
+ graph.scrollCellToVisible(vertex);
+ }
+};
+
+/**
+ * Function: installDropHandler
+ *
+ * Makes the given img draggable using the given function for handling a
+ * drop event.
+ *
+ * Parameters:
+ *
+ * img - DOM node that represents the image.
+ * dropHandler - Function that handles a drop of the image.
+ */
+mxDefaultToolbar.prototype.installDropHandler = function (img, dropHandler)
+{
+ var sprite = document.createElement('img');
+ sprite.setAttribute('src', img.getAttribute('src'));
+
+ // Handles delayed loading of the images
+ var loader = mxUtils.bind(this, function(evt)
+ {
+ // Preview uses the image node with double size. Later this can be
+ // changed to use a separate preview and guides, but for this the
+ // dropHandler must use the additional x- and y-arguments and the
+ // dragsource which makeDraggable returns much be configured to
+ // use guides via mxDragSource.isGuidesEnabled.
+ sprite.style.width = (2 * img.offsetWidth) + 'px';
+ sprite.style.height = (2 * img.offsetHeight) + 'px';
+
+ mxUtils.makeDraggable(img, this.editor.graph, dropHandler,
+ sprite);
+ mxEvent.removeListener(sprite, 'load', loader);
+ });
+
+ if (mxClient.IS_IE)
+ {
+ loader();
+ }
+ else
+ {
+ mxEvent.addListener(sprite, 'load', loader);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the <toolbar> associated with this object and removes all
+ * installed listeners. This does normally not need to be called, the
+ * <toolbar> is destroyed automatically when the window unloads (in IE) by
+ * <mxEditor>.
+ */
+mxDefaultToolbar.prototype.destroy = function ()
+{
+ if (this.resetHandler != null)
+ {
+ this.editor.graph.removeListener('dblclick', this.resetHandler);
+ this.editor.removeListener('escape', this.resetHandler);
+ this.resetHandler = null;
+ }
+
+ if (this.toolbar != null)
+ {
+ this.toolbar.destroy();
+ this.toolbar = null;
+ }
+};
diff --git a/src/js/editor/mxEditor.js b/src/js/editor/mxEditor.js
new file mode 100644
index 0000000..9c57a9c
--- /dev/null
+++ b/src/js/editor/mxEditor.js
@@ -0,0 +1,3220 @@
+/**
+ * $Id: mxEditor.js,v 1.231 2012-12-03 18:02:25 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEditor
+ *
+ * Extends <mxEventSource> to implement a application wrapper for a graph that
+ * adds <actions>, I/O using <mxCodec>, auto-layout using <mxLayoutManager>,
+ * command history using <undoManager>, and standard dialogs and widgets, eg.
+ * properties, help, outline, toolbar, and popupmenu. It also adds <templates>
+ * to be used as cells in toolbars, auto-validation using the <validation>
+ * flag, attribute cycling using <cycleAttributeValues>, higher-level events
+ * such as <root>, and backend integration using <urlPost>, <urlImage>,
+ * <urlInit>, <urlNotify> and <urlPoll>.
+ *
+ * Actions:
+ *
+ * Actions are functions stored in the <actions> array under their names. The
+ * functions take the <mxEditor> as the first, and an optional <mxCell> as the
+ * second argument and are invoked using <execute>. Any additional arguments
+ * passed to execute are passed on to the action as-is.
+ *
+ * A list of built-in actions is available in the <addActions> description.
+ *
+ * Read/write Diagrams:
+ *
+ * To read a diagram from an XML string, for example from a textfield within the
+ * page, the following code is used:
+ *
+ * (code)
+ * var doc = mxUtils.parseXML(xmlString);
+ * var node = doc.documentElement;
+ * editor.readGraphModel(node);
+ * (end)
+ *
+ * For reading a diagram from a remote location, use the <open> method.
+ *
+ * To save diagrams in XML on a server, you can set the <urlPost> variable.
+ * This variable will be used in <getUrlPost> to construct a URL for the post
+ * request that is issued in the <save> method. The post request contains the
+ * XML representation of the diagram as returned by <writeGraphModel> in the
+ * xml parameter.
+ *
+ * On the server side, the post request is processed using standard
+ * technologies such as Java Servlets, CGI, .NET or ASP.
+ *
+ * Here are some examples of processing a post request in various languages.
+ *
+ * - Java: URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;")
+ *
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image, but not
+ * if the XML is passed back to the client-side.
+ *
+ * - .NET: HttpUtility.UrlDecode(context.Request.Params["xml"])
+ * - PHP: urldecode($_POST["xml"])
+ *
+ * Creating images:
+ *
+ * A backend (Java, PHP or C#) is required for creating images. The
+ * distribution contains an example for each backend (ImageHandler.java,
+ * ImageHandler.cs and graph.php). More information about using a backend
+ * to create images can be found in the readme.html files. Note that the
+ * preview is implemented using VML/SVG in the browser and does not require
+ * a backend. The backend is only required to creates images (bitmaps).
+ *
+ * Special characters:
+ *
+ * Note There are five characters that should always appear in XML content as
+ * escapes, so that they do not interact with the syntax of the markup. These
+ * are part of the language for all documents based on XML and for HTML.
+ *
+ * - &lt; (<)
+ * - &gt; (>)
+ * - &amp; (&)
+ * - &quot; (")
+ * - &apos; (')
+ *
+ * Although it is part of the XML language, &apos; is not defined in HTML.
+ * For this reason the XHTML specification recommends instead the use of
+ * &#39; if text may be passed to a HTML user agent.
+ *
+ * If you are having problems with special characters on the server-side then
+ * you may want to try the <escapePostData> flag.
+ *
+ * For converting decimal escape sequences inside strings, a user has provided
+ * us with the following function:
+ *
+ * (code)
+ * function html2js(text)
+ * {
+ * var entitySearch = /&#[0-9]+;/;
+ * var entity;
+ *
+ * while (entity = entitySearch.exec(text))
+ * {
+ * var charCode = entity[0].substring(2, entity[0].length -1);
+ * text = text.substring(0, entity.index)
+ * + String.fromCharCode(charCode)
+ * + text.substring(entity.index + entity[0].length);
+ * }
+ *
+ * return text;
+ * }
+ * (end)
+ *
+ * Otherwise try using hex escape sequences and the built-in unescape function
+ * for converting such strings.
+ *
+ * Local Files:
+ *
+ * For saving and opening local files, no standardized method exists that
+ * works across all browsers. The recommended way of dealing with local files
+ * is to create a backend that streams the XML data back to the browser (echo)
+ * as an attachment so that a Save-dialog is displayed on the client-side and
+ * the file can be saved to the local disk.
+ *
+ * For example, in PHP the code that does this looks as follows.
+ *
+ * (code)
+ * $xml = stripslashes($_POST["xml"]);
+ * header("Content-Disposition: attachment; filename=\"diagram.xml\"");
+ * echo($xml);
+ * (end)
+ *
+ * To open a local file, the file should be uploaded via a form in the browser
+ * and then opened from the server in the editor.
+ *
+ * Cell Properties:
+ *
+ * The properties displayed in the properties dialog are the attributes and
+ * values of the cell's user object, which is an XML node. The XML node is
+ * defined in the templates section of the config file.
+ *
+ * The templates are stored in <mxEditor.templates> and contain cells which
+ * are cloned at insertion time to create new vertices by use of drag and
+ * drop from the toolbar. Each entry in the toolbar for adding a new vertex
+ * must refer to an existing template.
+ *
+ * In the following example, the task node is a business object and only the
+ * mxCell node and its mxGeometry child contain graph information:
+ *
+ * (code)
+ * <Task label="Task" description="">
+ * <mxCell vertex="true">
+ * <mxGeometry as="geometry" width="72" height="32"/>
+ * </mxCell>
+ * </Task>
+ * (end)
+ *
+ * The idea is that the XML representation is inverse from the in-memory
+ * representation: The outer XML node is the user object and the inner node is
+ * the cell. This means the user object of the cell is the Task node with no
+ * children for the above example:
+ *
+ * (code)
+ * <Task label="Task" description=""/>
+ * (end)
+ *
+ * The Task node can have any tag name, attributes and child nodes. The
+ * <mxCodec> will use the XML hierarchy as the user object, while removing the
+ * "known annotations", such as the mxCell node. At save-time the cell data
+ * will be "merged" back into the user object. The user object is only modified
+ * via the properties dialog during the lifecycle of the cell.
+ *
+ * In the default implementation of <createProperties>, the user object's
+ * attributes are put into a form for editing. Attributes are changed using
+ * the <mxCellAttributeChange> action in the model. The dialog can be replaced
+ * by overriding the <createProperties> hook or by replacing the showProperties
+ * action in <actions>. Alternatively, the entry in the config file's popupmenu
+ * section can be modified to invoke a different action.
+ *
+ * If you want to displey the properties dialog on a doubleclick, you can set
+ * <mxEditor.dblClickAction> to showProperties as follows:
+ *
+ * (code)
+ * editor.dblClickAction = 'showProperties';
+ * (end)
+ *
+ * Popupmenu and Toolbar:
+ *
+ * The toolbar and popupmenu are typically configured using the respective
+ * sections in the config file, that is, the popupmenu is defined as follows:
+ *
+ * (code)
+ * <mxEditor>
+ * <mxDefaultPopupMenu as="popupHandler">
+ * <add as="cut" action="cut" icon="images/cut.gif"/>
+ * ...
+ * (end)
+ *
+ * New entries can be added to the toolbar by inserting an add-node into the
+ * above configuration. Existing entries may be removed and changed by
+ * modifying or removing the respective entries in the configuration.
+ * The configuration is read by the <mxDefaultPopupMenuCodec>, the format of the
+ * configuration is explained in <mxDefaultPopupMenu.decode>.
+ *
+ * The toolbar is defined in the mxDefaultToolbar section. Items can be added
+ * and removed in this section.
+ *
+ * (code)
+ * <mxEditor>
+ * <mxDefaultToolbar>
+ * <add as="save" action="save" icon="images/save.gif"/>
+ * <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"/>
+ * ...
+ * (end)
+ *
+ * The format of the configuration is described in
+ * <mxDefaultToolbarCodec.decode>.
+ *
+ * Ids:
+ *
+ * For the IDs, there is an implicit behaviour in <mxCodec>: It moves the Id
+ * from the cell to the user object at encoding time and vice versa at decoding
+ * time. For example, if the Task node from above has an id attribute, then
+ * the <mxCell.id> of the corresponding cell will have this value. If there
+ * is no Id collision in the model, then the cell may be retrieved using this
+ * Id with the <mxGraphModel.getCell> function. If there is a collision, a new
+ * Id will be created for the cell using <mxGraphModel.createId>. At encoding
+ * time, this new Id will replace the value previously stored under the id
+ * attribute in the Task node.
+ *
+ * See <mxEditorCodec>, <mxDefaultToolbarCodec> and <mxDefaultPopupMenuCodec>
+ * for information about configuring the editor and user interface.
+ *
+ * Programmatically inserting cells:
+ *
+ * For inserting a new cell, say, by clicking a button in the document,
+ * the following code can be used. This requires an reference to the editor.
+ *
+ * (code)
+ * var userObject = new Object();
+ * var parent = editor.graph.getDefaultParent();
+ * var model = editor.graph.model;
+ * model.beginUpdate();
+ * try
+ * {
+ * editor.graph.insertVertex(parent, null, userObject, 20, 20, 80, 30);
+ * }
+ * finally
+ * {
+ * model.endUpdate();
+ * }
+ * (end)
+ *
+ * If a template cell from the config file should be inserted, then a clone
+ * of the template can be created as follows. The clone is then inserted using
+ * the add function instead of addVertex.
+ *
+ * (code)
+ * var template = editor.templates['task'];
+ * var clone = editor.graph.model.cloneCell(template);
+ * (end)
+ *
+ * Resources:
+ *
+ * resources/editor - Language resources for mxEditor
+ *
+ * Callback: onInit
+ *
+ * Called from within the constructor. In the callback,
+ * "this" refers to the editor instance.
+ *
+ * Cookie: mxgraph=seen
+ *
+ * Set when the editor is started. Never expires. Use
+ * <resetFirstTime> to reset this cookie. This cookie
+ * only exists if <onInit> is implemented.
+ *
+ * Event: mxEvent.OPEN
+ *
+ * Fires after a file was opened in <open>. The <code>filename</code> property
+ * contains the filename that was used. The same value is also available in
+ * <filename>.
+ *
+ * Event: mxEvent.SAVE
+ *
+ * Fires after the current file was saved in <save>. The <code>url</code>
+ * property contains the URL that was used for saving.
+ *
+ * Event: mxEvent.POST
+ *
+ * Fires if a successful response was received in <postDiagram>. The
+ * <code>request</code> property contains the <mxXmlRequest>, the
+ * <code>url</code> and <code>data</code> properties contain the URL and the
+ * data that were used in the post request.
+ *
+ * Event: mxEvent.ROOT
+ *
+ * Fires when the current root has changed, or when the title of the current
+ * root has changed. This event has no properties.
+ *
+ * Event: mxEvent.SESSION
+ *
+ * Fires when anything in the session has changed. The <code>session</code>
+ * property contains the respective <mxSession>.
+ *
+ * Event: mxEvent.BEFORE_ADD_VERTEX
+ *
+ * Fires before a vertex is added in <addVertex>. The <code>vertex</code>
+ * property contains the new vertex and the <code>parent</code> property
+ * contains its parent.
+ *
+ * Event: mxEvent.ADD_VERTEX
+ *
+ * Fires between begin- and endUpdate in <addVertex>. The <code>vertex</code>
+ * property contains the vertex that is being inserted.
+ *
+ * Event: mxEvent.AFTER_ADD_VERTEX
+ *
+ * Fires after a vertex was inserted and selected in <addVertex>. The
+ * <code>vertex</code> property contains the new vertex.
+ *
+ * Example:
+ *
+ * For starting an in-place edit after a new vertex has been added to the
+ * graph, the following code can be used.
+ *
+ * (code)
+ * editor.addListener(mxEvent.AFTER_ADD_VERTEX, function(sender, evt)
+ * {
+ * var vertex = evt.getProperty('vertex');
+ *
+ * if (editor.graph.isCellEditable(vertex))
+ * {
+ * editor.graph.startEditingAtCell(vertex);
+ * }
+ * });
+ * (end)
+ *
+ * Event: mxEvent.ESCAPE
+ *
+ * Fires when the escape key is pressed. The <code>event</code> property
+ * contains the key event.
+ *
+ * Constructor: mxEditor
+ *
+ * Constructs a new editor. This function invokes the <onInit> callback
+ * upon completion.
+ *
+ * Example:
+ *
+ * (code)
+ * var config = mxUtils.load('config/diagrameditor.xml').getDocumentElement();
+ * var editor = new mxEditor(config);
+ * (end)
+ *
+ * Parameters:
+ *
+ * config - Optional XML node that contains the configuration.
+ */
+function mxEditor(config)
+{
+ this.actions = [];
+ this.addActions();
+
+ // Executes the following only if a document has been instanciated.
+ // That is, don't execute when the editorcodec is setup.
+ if (document.body != null)
+ {
+ // Defines instance fields
+ this.cycleAttributeValues = [];
+ this.popupHandler = new mxDefaultPopupMenu();
+ this.undoManager = new mxUndoManager();
+
+ // Creates the graph and toolbar without the containers
+ this.graph = this.createGraph();
+ this.toolbar = this.createToolbar();
+
+ // Creates the global keyhandler (requires graph instance)
+ this.keyHandler = new mxDefaultKeyHandler(this);
+
+ // Configures the editor using the URI
+ // which was passed to the ctor
+ this.configure(config);
+
+ // Assigns the swimlaneIndicatorColorAttribute on the graph
+ this.graph.swimlaneIndicatorColorAttribute = this.cycleAttributeName;
+
+ // Initializes the session if the urlInit
+ // member field of this editor is set.
+ if (!mxClient.IS_LOCAL && this.urlInit != null)
+ {
+ this.session = this.createSession();
+ }
+
+ // Checks ifthe <onInit> hook has been set
+ if (this.onInit != null)
+ {
+ // Invokes the <onInit> hook
+ this.onInit();
+ }
+
+ // Automatic deallocation of memory
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+ {
+ this.destroy();
+ }));
+ }
+ }
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+ mxResources.add(mxClient.basePath+'/resources/editor');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxEditor.prototype = new mxEventSource();
+mxEditor.prototype.constructor = mxEditor;
+
+/**
+ * Group: Controls and Handlers
+ */
+
+/**
+ * Variable: askZoomResource
+ *
+ * Specifies the resource key for the zoom dialog. If the resource for this
+ * key does not exist then the value is used as the error message. Default
+ * is 'askZoom'.
+ */
+mxEditor.prototype.askZoomResource = (mxClient.language != 'none') ? 'askZoom' : '';
+
+/**
+ * Variable: lastSavedResource
+ *
+ * Specifies the resource key for the last saved info. If the resource for
+ * this key does not exist then the value is used as the error message.
+ * Default is 'lastSaved'.
+ */
+mxEditor.prototype.lastSavedResource = (mxClient.language != 'none') ? 'lastSaved' : '';
+
+/**
+ * Variable: currentFileResource
+ *
+ * Specifies the resource key for the current file info. If the resource for
+ * this key does not exist then the value is used as the error message.
+ * Default is 'lastSaved'.
+ */
+mxEditor.prototype.currentFileResource = (mxClient.language != 'none') ? 'currentFile' : '';
+
+/**
+ * Variable: propertiesResource
+ *
+ * Specifies the resource key for the properties window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'properties'.
+ */
+mxEditor.prototype.propertiesResource = (mxClient.language != 'none') ? 'properties' : '';
+
+/**
+ * Variable: tasksResource
+ *
+ * Specifies the resource key for the tasks window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'tasks'.
+ */
+mxEditor.prototype.tasksResource = (mxClient.language != 'none') ? 'tasks' : '';
+
+/**
+ * Variable: helpResource
+ *
+ * Specifies the resource key for the help window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'help'.
+ */
+mxEditor.prototype.helpResource = (mxClient.language != 'none') ? 'help' : '';
+
+/**
+ * Variable: outlineResource
+ *
+ * Specifies the resource key for the outline window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'outline'.
+ */
+mxEditor.prototype.outlineResource = (mxClient.language != 'none') ? 'outline' : '';
+
+/**
+ * Variable: outline
+ *
+ * Reference to the <mxWindow> that contains the outline. The <mxOutline>
+ * is stored in outline.outline.
+ */
+mxEditor.prototype.outline = null;
+
+/**
+ * Variable: graph
+ *
+ * Holds a <mxGraph> for displaying the diagram. The graph
+ * is created in <setGraphContainer>.
+ */
+mxEditor.prototype.graph = null;
+
+/**
+ * Variable: graphRenderHint
+ *
+ * Holds the render hint used for creating the
+ * graph in <setGraphContainer>. See <mxGraph>.
+ * Default is null.
+ */
+mxEditor.prototype.graphRenderHint = null;
+
+/**
+ * Variable: toolbar
+ *
+ * Holds a <mxDefaultToolbar> for displaying the toolbar. The
+ * toolbar is created in <setToolbarContainer>.
+ */
+mxEditor.prototype.toolbar = null;
+
+/**
+ * Variable: session
+ *
+ * Holds a <mxSession> instance associated with this editor.
+ */
+mxEditor.prototype.session = null;
+
+/**
+ * Variable: status
+ *
+ * DOM container that holds the statusbar. Default is null.
+ * Use <setStatusContainer> to set this value.
+ */
+mxEditor.prototype.status = null;
+
+/**
+ * Variable: popupHandler
+ *
+ * Holds a <mxDefaultPopupMenu> for displaying
+ * popupmenus.
+ */
+mxEditor.prototype.popupHandler = null;
+
+/**
+ * Variable: undoManager
+ *
+ * Holds an <mxUndoManager> for the command history.
+ */
+mxEditor.prototype.undoManager = null;
+
+/**
+ * Variable: keyHandler
+ *
+ * Holds a <mxDefaultKeyHandler> for handling keyboard events.
+ * The handler is created in <setGraphContainer>.
+ */
+mxEditor.prototype.keyHandler = null;
+
+/**
+ * Group: Actions and Options
+ */
+
+/**
+ * Variable: actions
+ *
+ * Maps from actionnames to actions, which are functions taking
+ * the editor and the cell as arguments. Use <addAction>
+ * to add or replace an action and <execute> to execute an action
+ * by name, passing the cell to be operated upon as the second
+ * argument.
+ */
+mxEditor.prototype.actions = null;
+
+/**
+ * Variable: dblClickAction
+ *
+ * Specifies the name of the action to be executed
+ * when a cell is double clicked. Default is edit.
+ *
+ * To handle a singleclick, use the following code.
+ *
+ * (code)
+ * editor.graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ * var e = evt.getProperty('event');
+ * var cell = evt.getProperty('cell');
+ *
+ * if (cell != null && !e.isConsumed())
+ * {
+ * // Do something useful with cell...
+ * e.consume();
+ * }
+ * });
+ * (end)
+ */
+mxEditor.prototype.dblClickAction = 'edit';
+
+/**
+ * Variable: swimlaneRequired
+ *
+ * Specifies if new cells must be inserted
+ * into an existing swimlane. Otherwise, cells
+ * that are not swimlanes can be inserted as
+ * top-level cells. Default is false.
+ */
+mxEditor.prototype.swimlaneRequired = false;
+
+/**
+ * Variable: disableContextMenu
+ *
+ * Specifies if the context menu should be disabled in the graph container.
+ * Default is true.
+ */
+mxEditor.prototype.disableContextMenu = true;
+
+/**
+ * Group: Templates
+ */
+
+/**
+ * Variable: insertFunction
+ *
+ * Specifies the function to be used for inserting new
+ * cells into the graph. This is assigned from the
+ * <mxDefaultToolbar> if a vertex-tool is clicked.
+ */
+mxEditor.prototype.insertFunction = null;
+
+/**
+ * Variable: forcedInserting
+ *
+ * Specifies if a new cell should be inserted on a single
+ * click even using <insertFunction> if there is a cell
+ * under the mousepointer, otherwise the cell under the
+ * mousepointer is selected. Default is false.
+ */
+mxEditor.prototype.forcedInserting = false;
+
+/**
+ * Variable: templates
+ *
+ * Maps from names to protoype cells to be used
+ * in the toolbar for inserting new cells into
+ * the diagram.
+ */
+mxEditor.prototype.templates = null;
+
+/**
+ * Variable: defaultEdge
+ *
+ * Prototype edge cell that is used for creating
+ * new edges.
+ */
+mxEditor.prototype.defaultEdge = null;
+
+/**
+ * Variable: defaultEdgeStyle
+ *
+ * Specifies the edge style to be returned in <getEdgeStyle>.
+ * Default is null.
+ */
+mxEditor.prototype.defaultEdgeStyle = null;
+
+/**
+ * Variable: defaultGroup
+ *
+ * Prototype group cell that is used for creating
+ * new groups.
+ */
+mxEditor.prototype.defaultGroup = null;
+
+/**
+ * Variable: graphRenderHint
+ *
+ * Default size for the border of new groups. If null,
+ * then then <mxGraph.gridSize> is used. Default is
+ * null.
+ */
+mxEditor.prototype.groupBorderSize = null;
+
+/**
+ * Group: Backend Integration
+ */
+
+/**
+ * Variable: filename
+ *
+ * Contains the URL of the last opened file as a string.
+ * Default is null.
+ */
+mxEditor.prototype.filename = null;
+
+/**
+ * Variable: lineFeed
+ *
+ * Character to be used for encoding linefeeds in <save>. Default is '&#xa;'.
+ */
+mxEditor.prototype.linefeed = '&#xa;';
+
+/**
+ * Variable: postParameterName
+ *
+ * Specifies if the name of the post parameter that contains the diagram
+ * data in a post request to the server. Default is xml.
+ */
+mxEditor.prototype.postParameterName = 'xml';
+
+/**
+ * Variable: escapePostData
+ *
+ * Specifies if the data in the post request for saving a diagram
+ * should be converted using encodeURIComponent. Default is true.
+ */
+mxEditor.prototype.escapePostData = true;
+
+/**
+ * Variable: urlPost
+ *
+ * Specifies the URL to be used for posting the diagram
+ * to a backend in <save>.
+ */
+mxEditor.prototype.urlPost = null;
+
+/**
+ * Variable: urlImage
+ *
+ * Specifies the URL to be used for creating a bitmap of
+ * the graph in the image action.
+ */
+mxEditor.prototype.urlImage = null;
+
+/**
+ * Variable: urlInit
+ *
+ * Specifies the URL to be used for initializing the session.
+ */
+mxEditor.prototype.urlInit = null;
+
+/**
+ * Variable: urlNotify
+ *
+ * Specifies the URL to be used for notifying the backend
+ * in the session.
+ */
+mxEditor.prototype.urlNotify = null;
+
+/**
+ * Variable: urlPoll
+ *
+ * Specifies the URL to be used for polling in the session.
+ */
+mxEditor.prototype.urlPoll = null;
+
+/**
+ * Group: Autolayout
+ */
+
+/**
+ * Variable: horizontalFlow
+ *
+ * Specifies the direction of the flow
+ * in the diagram. This is used in the
+ * layout algorithms. Default is false,
+ * ie. vertical flow.
+ */
+mxEditor.prototype.horizontalFlow = false;
+
+/**
+ * Variable: layoutDiagram
+ *
+ * Specifies if the top-level elements in the
+ * diagram should be layed out using a vertical
+ * or horizontal stack depending on the setting
+ * of <horizontalFlow>. The spacing between the
+ * swimlanes is specified by <swimlaneSpacing>.
+ * Default is false.
+ *
+ * If the top-level elements are swimlanes, then
+ * the intra-swimlane layout is activated by
+ * the <layoutSwimlanes> switch.
+ */
+mxEditor.prototype.layoutDiagram = false;
+
+/**
+ * Variable: swimlaneSpacing
+ *
+ * Specifies the spacing between swimlanes if
+ * automatic layout is turned on in
+ * <layoutDiagram>. Default is 0.
+ */
+mxEditor.prototype.swimlaneSpacing = 0;
+
+/**
+ * Variable: maintainSwimlanes
+ *
+ * Specifies if the swimlanes should be kept at the same
+ * width or height depending on the setting of
+ * <horizontalFlow>. Default is false.
+ *
+ * For horizontal flows, all swimlanes
+ * have the same height and for vertical flows, all swimlanes
+ * have the same width. Furthermore, the swimlanes are
+ * automatically "stacked" if <layoutDiagram> is true.
+ */
+mxEditor.prototype.maintainSwimlanes = false;
+
+/**
+ * Variable: layoutSwimlanes
+ *
+ * Specifies if the children of swimlanes should
+ * be layed out, either vertically or horizontally
+ * depending on <horizontalFlow>.
+ * Default is false.
+ */
+mxEditor.prototype.layoutSwimlanes = false;
+
+/**
+ * Group: Attribute Cycling
+ */
+
+/**
+ * Variable: cycleAttributeValues
+ *
+ * Specifies the attribute values to be cycled when
+ * inserting new swimlanes. Default is an empty
+ * array.
+ */
+mxEditor.prototype.cycleAttributeValues = null;
+
+/**
+ * Variable: cycleAttributeIndex
+ *
+ * Index of the last consumed attribute index. If a new
+ * swimlane is inserted, then the <cycleAttributeValues>
+ * at this index will be used as the value for
+ * <cycleAttributeName>. Default is 0.
+ */
+mxEditor.prototype.cycleAttributeIndex = 0;
+
+/**
+ * Variable: cycleAttributeName
+ *
+ * Name of the attribute to be assigned a <cycleAttributeValues>
+ * when inserting new swimlanes. Default is fillColor.
+ */
+mxEditor.prototype.cycleAttributeName = 'fillColor';
+
+/**
+ * Group: Windows
+ */
+
+/**
+ * Variable: tasks
+ *
+ * Holds the <mxWindow> created in <showTasks>.
+ */
+mxEditor.prototype.tasks = null;
+
+/**
+ * Variable: tasksWindowImage
+ *
+ * Icon for the tasks window.
+ */
+mxEditor.prototype.tasksWindowImage = null;
+
+/**
+ * Variable: tasksTop
+ *
+ * Specifies the top coordinate of the tasks window in pixels.
+ * Default is 20.
+ */
+mxEditor.prototype.tasksTop = 20;
+
+/**
+ * Variable: help
+ *
+ * Holds the <mxWindow> created in <showHelp>.
+ */
+mxEditor.prototype.help = null;
+
+/**
+ * Variable: helpWindowImage
+ *
+ * Icon for the help window.
+ */
+mxEditor.prototype.helpWindowImage = null;
+
+/**
+ * Variable: urlHelp
+ *
+ * Specifies the URL to be used for the contents of the
+ * Online Help window. This is usually specified in the
+ * resources file under urlHelp for language-specific
+ * online help support.
+ */
+mxEditor.prototype.urlHelp = null;
+
+/**
+ * Variable: helpWidth
+ *
+ * Specifies the width of the help window in pixels.
+ * Default is 300.
+ */
+mxEditor.prototype.helpWidth = 300;
+
+/**
+ * Variable: helpWidth
+ *
+ * Specifies the width of the help window in pixels.
+ * Default is 260.
+ */
+mxEditor.prototype.helpHeight = 260;
+
+/**
+ * Variable: propertiesWidth
+ *
+ * Specifies the width of the properties window in pixels.
+ * Default is 240.
+ */
+mxEditor.prototype.propertiesWidth = 240;
+
+/**
+ * Variable: propertiesHeight
+ *
+ * Specifies the height of the properties window in pixels.
+ * If no height is specified then the window will be automatically
+ * sized to fit its contents. Default is null.
+ */
+mxEditor.prototype.propertiesHeight = null;
+
+/**
+ * Variable: movePropertiesDialog
+ *
+ * Specifies if the properties dialog should be automatically
+ * moved near the cell it is displayed for, otherwise the
+ * dialog is not moved. This value is only taken into
+ * account if the dialog is already visible. Default is false.
+ */
+mxEditor.prototype.movePropertiesDialog = false;
+
+/**
+ * Variable: validating
+ *
+ * Specifies if <mxGraph.validateGraph> should automatically be invoked after
+ * each change. Default is false.
+ */
+mxEditor.prototype.validating = false;
+
+/**
+ * Variable: modified
+ *
+ * True if the graph has been modified since it was last saved.
+ */
+mxEditor.prototype.modified = false;
+
+/**
+ * Function: isModified
+ *
+ * Returns <modified>.
+ */
+mxEditor.prototype.isModified = function ()
+{
+ return this.modified;
+};
+
+/**
+ * Function: setModified
+ *
+ * Sets <modified> to the specified boolean value.
+ */
+mxEditor.prototype.setModified = function (value)
+{
+ this.modified = value;
+};
+
+/**
+ * Function: addActions
+ *
+ * Adds the built-in actions to the editor instance.
+ *
+ * save - Saves the graph using <urlPost>.
+ * print - Shows the graph in a new print preview window.
+ * show - Shows the graph in a new window.
+ * exportImage - Shows the graph as a bitmap image using <getUrlImage>.
+ * refresh - Refreshes the graph's display.
+ * cut - Copies the current selection into the clipboard
+ * and removes it from the graph.
+ * copy - Copies the current selection into the clipboard.
+ * paste - Pastes the clipboard into the graph.
+ * delete - Removes the current selection from the graph.
+ * group - Puts the current selection into a new group.
+ * ungroup - Removes the selected groups and selects the children.
+ * undo - Undoes the last change on the graph model.
+ * redo - Redoes the last change on the graph model.
+ * zoom - Sets the zoom via a dialog.
+ * zoomIn - Zooms into the graph.
+ * zoomOut - Zooms out of the graph
+ * actualSize - Resets the scale and translation on the graph.
+ * fit - Changes the scale so that the graph fits into the window.
+ * showProperties - Shows the properties dialog.
+ * selectAll - Selects all cells.
+ * selectNone - Clears the selection.
+ * selectVertices - Selects all vertices.
+ * selectEdges = Selects all edges.
+ * edit - Starts editing the current selection cell.
+ * enterGroup - Drills down into the current selection cell.
+ * exitGroup - Moves up in the drilling hierachy
+ * home - Moves to the topmost parent in the drilling hierarchy
+ * selectPrevious - Selects the previous cell.
+ * selectNext - Selects the next cell.
+ * selectParent - Selects the parent of the selection cell.
+ * selectChild - Selects the first child of the selection cell.
+ * collapse - Collapses the currently selected cells.
+ * expand - Expands the currently selected cells.
+ * bold - Toggle bold text style.
+ * italic - Toggle italic text style.
+ * underline - Toggle underline text style.
+ * shadow - Toggle shadow text style.
+ * alignCellsLeft - Aligns the selection cells at the left.
+ * alignCellsCenter - Aligns the selection cells in the center.
+ * alignCellsRight - Aligns the selection cells at the right.
+ * alignCellsTop - Aligns the selection cells at the top.
+ * alignCellsMiddle - Aligns the selection cells in the middle.
+ * alignCellsBottom - Aligns the selection cells at the bottom.
+ * alignFontLeft - Sets the horizontal text alignment to left.
+ * alignFontCenter - Sets the horizontal text alignment to center.
+ * alignFontRight - Sets the horizontal text alignment to right.
+ * alignFontTop - Sets the vertical text alignment to top.
+ * alignFontMiddle - Sets the vertical text alignment to middle.
+ * alignFontBottom - Sets the vertical text alignment to bottom.
+ * toggleTasks - Shows or hides the tasks window.
+ * toggleHelp - Shows or hides the help window.
+ * toggleOutline - Shows or hides the outline window.
+ * toggleConsole - Shows or hides the console window.
+ */
+mxEditor.prototype.addActions = function ()
+{
+ this.addAction('save', function(editor)
+ {
+ editor.save();
+ });
+
+ this.addAction('print', function(editor)
+ {
+ var preview = new mxPrintPreview(editor.graph, 1);
+ preview.open();
+ });
+
+ this.addAction('show', function(editor)
+ {
+ mxUtils.show(editor.graph, null, 10, 10);
+ });
+
+ this.addAction('exportImage', function(editor)
+ {
+ var url = editor.getUrlImage();
+
+ if (url == null || mxClient.IS_LOCAL)
+ {
+ editor.execute('show');
+ }
+ else
+ {
+ var node = mxUtils.getViewXml(editor.graph, 1);
+ var xml = mxUtils.getXml(node, '\n');
+
+ mxUtils.submit(url, editor.postParameterName + '=' +
+ encodeURIComponent(xml), document, '_blank');
+ }
+ });
+
+ this.addAction('refresh', function(editor)
+ {
+ editor.graph.refresh();
+ });
+
+ this.addAction('cut', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ mxClipboard.cut(editor.graph);
+ }
+ });
+
+ this.addAction('copy', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ mxClipboard.copy(editor.graph);
+ }
+ });
+
+ this.addAction('paste', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ mxClipboard.paste(editor.graph);
+ }
+ });
+
+ this.addAction('delete', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.removeCells();
+ }
+ });
+
+ this.addAction('group', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setSelectionCell(editor.groupCells());
+ }
+ });
+
+ this.addAction('ungroup', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setSelectionCells(editor.graph.ungroupCells());
+ }
+ });
+
+ this.addAction('removeFromParent', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.removeCellsFromParent();
+ }
+ });
+
+ this.addAction('undo', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.undo();
+ }
+ });
+
+ this.addAction('redo', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.redo();
+ }
+ });
+
+ this.addAction('zoomIn', function(editor)
+ {
+ editor.graph.zoomIn();
+ });
+
+ this.addAction('zoomOut', function(editor)
+ {
+ editor.graph.zoomOut();
+ });
+
+ this.addAction('actualSize', function(editor)
+ {
+ editor.graph.zoomActual();
+ });
+
+ this.addAction('fit', function(editor)
+ {
+ editor.graph.fit();
+ });
+
+ this.addAction('showProperties', function(editor, cell)
+ {
+ editor.showProperties(cell);
+ });
+
+ this.addAction('selectAll', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectAll();
+ }
+ });
+
+ this.addAction('selectNone', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.clearSelection();
+ }
+ });
+
+ this.addAction('selectVertices', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectVertices();
+ }
+ });
+
+ this.addAction('selectEdges', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectEdges();
+ }
+ });
+
+ this.addAction('edit', function(editor, cell)
+ {
+ if (editor.graph.isEnabled() &&
+ editor.graph.isCellEditable(cell))
+ {
+ editor.graph.startEditingAtCell(cell);
+ }
+ });
+
+ this.addAction('toBack', function(editor, cell)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.orderCells(true);
+ }
+ });
+
+ this.addAction('toFront', function(editor, cell)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.orderCells(false);
+ }
+ });
+
+ this.addAction('enterGroup', function(editor, cell)
+ {
+ editor.graph.enterGroup(cell);
+ });
+
+ this.addAction('exitGroup', function(editor)
+ {
+ editor.graph.exitGroup();
+ });
+
+ this.addAction('home', function(editor)
+ {
+ editor.graph.home();
+ });
+
+ this.addAction('selectPrevious', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectPreviousCell();
+ }
+ });
+
+ this.addAction('selectNext', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectNextCell();
+ }
+ });
+
+ this.addAction('selectParent', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectParentCell();
+ }
+ });
+
+ this.addAction('selectChild', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectChildCell();
+ }
+ });
+
+ this.addAction('collapse', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.foldCells(true);
+ }
+ });
+
+ this.addAction('collapseAll', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ var cells = editor.graph.getChildVertices();
+ editor.graph.foldCells(true, false, cells);
+ }
+ });
+
+ this.addAction('expand', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.foldCells(false);
+ }
+ });
+
+ this.addAction('expandAll', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ var cells = editor.graph.getChildVertices();
+ editor.graph.foldCells(false, false, cells);
+ }
+ });
+
+ this.addAction('bold', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.toggleCellStyleFlags(
+ mxConstants.STYLE_FONTSTYLE,
+ mxConstants.FONT_BOLD);
+ }
+ });
+
+ this.addAction('italic', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.toggleCellStyleFlags(
+ mxConstants.STYLE_FONTSTYLE,
+ mxConstants.FONT_ITALIC);
+ }
+ });
+
+ this.addAction('underline', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.toggleCellStyleFlags(
+ mxConstants.STYLE_FONTSTYLE,
+ mxConstants.FONT_UNDERLINE);
+ }
+ });
+
+ this.addAction('shadow', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.toggleCellStyleFlags(
+ mxConstants.STYLE_FONTSTYLE,
+ mxConstants.FONT_SHADOW);
+ }
+ });
+
+ this.addAction('alignCellsLeft', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_LEFT);
+ }
+ });
+
+ this.addAction('alignCellsCenter', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_CENTER);
+ }
+ });
+
+ this.addAction('alignCellsRight', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_RIGHT);
+ }
+ });
+
+ this.addAction('alignCellsTop', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_TOP);
+ }
+ });
+
+ this.addAction('alignCellsMiddle', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_MIDDLE);
+ }
+ });
+
+ this.addAction('alignCellsBottom', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_BOTTOM);
+ }
+ });
+
+ this.addAction('alignFontLeft', function(editor)
+ {
+
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_ALIGN,
+ mxConstants.ALIGN_LEFT);
+ });
+
+ this.addAction('alignFontCenter', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_ALIGN,
+ mxConstants.ALIGN_CENTER);
+ }
+ });
+
+ this.addAction('alignFontRight', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_ALIGN,
+ mxConstants.ALIGN_RIGHT);
+ }
+ });
+
+ this.addAction('alignFontTop', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_VERTICAL_ALIGN,
+ mxConstants.ALIGN_TOP);
+ }
+ });
+
+ this.addAction('alignFontMiddle', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_VERTICAL_ALIGN,
+ mxConstants.ALIGN_MIDDLE);
+ }
+ });
+
+ this.addAction('alignFontBottom', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_VERTICAL_ALIGN,
+ mxConstants.ALIGN_BOTTOM);
+ }
+ });
+
+ this.addAction('zoom', function(editor)
+ {
+ var current = editor.graph.getView().scale*100;
+ var scale = parseFloat(mxUtils.prompt(
+ mxResources.get(editor.askZoomResource) ||
+ editor.askZoomResource,
+ current))/100;
+
+ if (!isNaN(scale))
+ {
+ editor.graph.getView().setScale(scale);
+ }
+ });
+
+ this.addAction('toggleTasks', function(editor)
+ {
+ if (editor.tasks != null)
+ {
+ editor.tasks.setVisible(!editor.tasks.isVisible());
+ }
+ else
+ {
+ editor.showTasks();
+ }
+ });
+
+ this.addAction('toggleHelp', function(editor)
+ {
+ if (editor.help != null)
+ {
+ editor.help.setVisible(!editor.help.isVisible());
+ }
+ else
+ {
+ editor.showHelp();
+ }
+ });
+
+ this.addAction('toggleOutline', function(editor)
+ {
+ if (editor.outline == null)
+ {
+ editor.showOutline();
+ }
+ else
+ {
+ editor.outline.setVisible(!editor.outline.isVisible());
+ }
+ });
+
+ this.addAction('toggleConsole', function(editor)
+ {
+ mxLog.setVisible(!mxLog.isVisible());
+ });
+};
+
+/**
+ * Function: createSession
+ *
+ * Creates and returns and <mxSession> using <urlInit>, <urlPoll> and <urlNotify>.
+ */
+mxEditor.prototype.createSession = function ()
+{
+ // Routes any change events from the session
+ // through the editor and dispatches them as
+ // a session event.
+ var sessionChanged = mxUtils.bind(this, function(session)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.SESSION, 'session', session));
+ });
+
+ return this.connect(this.urlInit, this.urlPoll,
+ this.urlNotify, sessionChanged);
+};
+
+/**
+ * Function: configure
+ *
+ * Configures the editor using the specified node. To load the
+ * configuration from a given URL the following code can be used to obtain
+ * the XML node.
+ *
+ * (code)
+ * var node = mxUtils.load(url).getDocumentElement();
+ * (end)
+ *
+ * Parameters:
+ *
+ * node - XML node that contains the configuration.
+ */
+mxEditor.prototype.configure = function (node)
+{
+ if (node != null)
+ {
+ // Creates a decoder for the XML data
+ // and uses it to configure the editor
+ var dec = new mxCodec(node.ownerDocument);
+ dec.decode(node, this);
+
+ // Resets the counters, modified state and
+ // command history
+ this.resetHistory();
+ }
+};
+
+/**
+ * Function: resetFirstTime
+ *
+ * Resets the cookie that is used to remember if the editor has already
+ * been used.
+ */
+mxEditor.prototype.resetFirstTime = function ()
+{
+ document.cookie =
+ 'mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/';
+};
+
+/**
+ * Function: resetHistory
+ *
+ * Resets the command history, modified state and counters.
+ */
+mxEditor.prototype.resetHistory = function ()
+{
+ this.lastSnapshot = new Date().getTime();
+ this.undoManager.clear();
+ this.ignoredChanges = 0;
+ this.setModified(false);
+};
+
+/**
+ * Function: addAction
+ *
+ * Binds the specified actionname to the specified function.
+ *
+ * Parameters:
+ *
+ * actionname - String that specifies the name of the action
+ * to be added.
+ * funct - Function that implements the new action. The first
+ * argument of the function is the editor it is used
+ * with, the second argument is the cell it operates
+ * upon.
+ *
+ * Example:
+ * (code)
+ * editor.addAction('test', function(editor, cell)
+ * {
+ * mxUtils.alert("test "+cell);
+ * });
+ * (end)
+ */
+mxEditor.prototype.addAction = function (actionname, funct)
+{
+ this.actions[actionname] = funct;
+};
+
+/**
+ * Function: execute
+ *
+ * Executes the function with the given name in <actions> passing the
+ * editor instance and given cell as the first and second argument. All
+ * additional arguments are passed to the action as well. This method
+ * contains a try-catch block and displays an error message if an action
+ * causes an exception. The exception is re-thrown after the error
+ * message was displayed.
+ *
+ * Example:
+ *
+ * (code)
+ * editor.execute("showProperties", cell);
+ * (end)
+ */
+mxEditor.prototype.execute = function (actionname, cell, evt)
+{
+ var action = this.actions[actionname];
+
+ if (action != null)
+ {
+ try
+ {
+ // Creates the array of arguments by replacing the actionname
+ // with the editor instance in the args of this function
+ var args = arguments;
+ args[0] = this;
+
+ // Invokes the function on the editor using the args
+ action.apply(this, args);
+ }
+ catch (e)
+ {
+ mxUtils.error('Cannot execute ' + actionname +
+ ': ' + e.message, 280, true);
+
+ throw e;
+ }
+ }
+ else
+ {
+ mxUtils.error('Cannot find action '+actionname, 280, true);
+ }
+};
+
+/**
+ * Function: addTemplate
+ *
+ * Adds the specified template under the given name in <templates>.
+ */
+mxEditor.prototype.addTemplate = function (name, template)
+{
+ this.templates[name] = template;
+};
+
+/**
+ * Function: getTemplate
+ *
+ * Returns the template for the given name.
+ */
+mxEditor.prototype.getTemplate = function (name)
+{
+ return this.templates[name];
+};
+
+/**
+ * Function: createGraph
+ *
+ * Creates the <graph> for the editor. The graph is created with no
+ * container and is initialized from <setGraphContainer>.
+ */
+mxEditor.prototype.createGraph = function ()
+{
+ var graph = new mxGraph(null, null, this.graphRenderHint);
+
+ // Enables rubberband, tooltips, panning
+ graph.setTooltips(true);
+ graph.setPanning(true);
+
+ // Overrides the dblclick method on the graph to
+ // invoke the dblClickAction for a cell and reset
+ // the selection tool in the toolbar
+ this.installDblClickHandler(graph);
+
+ // Installs the command history
+ this.installUndoHandler(graph);
+
+ // Installs the handlers for the root event
+ this.installDrillHandler(graph);
+
+ // Installs the handler for validation
+ this.installChangeHandler(graph);
+
+ // Installs the handler for calling the
+ // insert function and consume the
+ // event if an insert function is defined
+ this.installInsertHandler(graph);
+
+ // Redirects the function for creating the
+ // popupmenu items
+ graph.panningHandler.factoryMethod =
+ mxUtils.bind(this, function(menu, cell, evt)
+ {
+ return this.createPopupMenu(menu, cell, evt);
+ });
+
+ // Redirects the function for creating
+ // new connections in the diagram
+ graph.connectionHandler.factoryMethod =
+ mxUtils.bind(this, function(source, target)
+ {
+ return this.createEdge(source, target);
+ });
+
+ // Maintains swimlanes and installs autolayout
+ this.createSwimlaneManager(graph);
+ this.createLayoutManager(graph);
+
+ return graph;
+};
+
+/**
+ * Function: createSwimlaneManager
+ *
+ * Sets the graph's container using <mxGraph.init>.
+ */
+mxEditor.prototype.createSwimlaneManager = function (graph)
+{
+ var swimlaneMgr = new mxSwimlaneManager(graph, false);
+
+ swimlaneMgr.isHorizontal = mxUtils.bind(this, function()
+ {
+ return this.horizontalFlow;
+ });
+
+ swimlaneMgr.isEnabled = mxUtils.bind(this, function()
+ {
+ return this.maintainSwimlanes;
+ });
+
+ return swimlaneMgr;
+};
+
+/**
+ * Function: createLayoutManager
+ *
+ * Creates a layout manager for the swimlane and diagram layouts, that
+ * is, the locally defined inter- and intraswimlane layouts.
+ */
+mxEditor.prototype.createLayoutManager = function (graph)
+{
+ var layoutMgr = new mxLayoutManager(graph);
+
+ var self = this; // closure
+ layoutMgr.getLayout = function(cell)
+ {
+ var layout = null;
+ var model = self.graph.getModel();
+
+ if (model.getParent(cell) != null)
+ {
+ // Executes the swimlane layout if a child of
+ // a swimlane has been changed. The layout is
+ // lazy created in createSwimlaneLayout.
+ if (self.layoutSwimlanes &&
+ graph.isSwimlane(cell))
+ {
+ if (self.swimlaneLayout == null)
+ {
+ self.swimlaneLayout = self.createSwimlaneLayout();
+ }
+
+ layout = self.swimlaneLayout;
+ }
+
+ // Executes the diagram layout if the modified
+ // cell is a top-level cell. The layout is
+ // lazy created in createDiagramLayout.
+ else if (self.layoutDiagram &&
+ (graph.isValidRoot(cell) ||
+ model.getParent(model.getParent(cell)) == null))
+ {
+ if (self.diagramLayout == null)
+ {
+ self.diagramLayout = self.createDiagramLayout();
+ }
+
+ layout = self.diagramLayout;
+ }
+ }
+
+ return layout;
+ };
+
+ return layoutMgr;
+};
+
+/**
+ * Function: setGraphContainer
+ *
+ * Sets the graph's container using <mxGraph.init>.
+ */
+mxEditor.prototype.setGraphContainer = function (container)
+{
+ if (this.graph.container == null)
+ {
+ // Creates the graph instance inside the given container and render hint
+ //this.graph = new mxGraph(container, null, this.graphRenderHint);
+ this.graph.init(container);
+
+ // Install rubberband selection as the last
+ // action handler in the chain
+ this.rubberband = new mxRubberband(this.graph);
+
+ // Disables the context menu
+ if (this.disableContextMenu)
+ {
+ mxEvent.disableContextMenu(container);
+ }
+
+ // Workaround for stylesheet directives in IE
+ if (mxClient.IS_QUIRKS)
+ {
+ new mxDivResizer(container);
+ }
+ }
+};
+
+/**
+ * Function: installDblClickHandler
+ *
+ * Overrides <mxGraph.dblClick> to invoke <dblClickAction>
+ * on a cell and reset the selection tool in the toolbar.
+ */
+mxEditor.prototype.installDblClickHandler = function (graph)
+{
+ // Installs a listener for double click events
+ graph.addListener(mxEvent.DOUBLE_CLICK,
+ mxUtils.bind(this, function(sender, evt)
+ {
+ var cell = evt.getProperty('cell');
+
+ if (cell != null &&
+ graph.isEnabled() &&
+ this.dblClickAction != null)
+ {
+ this.execute(this.dblClickAction, cell);
+ evt.consume();
+ }
+ })
+ );
+};
+
+/**
+ * Function: installUndoHandler
+ *
+ * Adds the <undoManager> to the graph model and the view.
+ */
+mxEditor.prototype.installUndoHandler = function (graph)
+{
+ var listener = mxUtils.bind(this, function(sender, evt)
+ {
+ var edit = evt.getProperty('edit');
+ this.undoManager.undoableEditHappened(edit);
+ });
+
+ graph.getModel().addListener(mxEvent.UNDO, listener);
+ graph.getView().addListener(mxEvent.UNDO, listener);
+
+ // Keeps the selection state in sync
+ var undoHandler = function(sender, evt)
+ {
+ var changes = evt.getProperty('edit').changes;
+ graph.setSelectionCells(graph.getSelectionCellsForChanges(changes));
+ };
+
+ this.undoManager.addListener(mxEvent.UNDO, undoHandler);
+ this.undoManager.addListener(mxEvent.REDO, undoHandler);
+};
+
+/**
+ * Function: installDrillHandler
+ *
+ * Installs listeners for dispatching the <root> event.
+ */
+mxEditor.prototype.installDrillHandler = function (graph)
+{
+ var listener = mxUtils.bind(this, function(sender)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.ROOT));
+ });
+
+ graph.getView().addListener(mxEvent.DOWN, listener);
+ graph.getView().addListener(mxEvent.UP, listener);
+};
+
+/**
+ * Function: installChangeHandler
+ *
+ * Installs the listeners required to automatically validate
+ * the graph. On each change of the root, this implementation
+ * fires a <root> event.
+ */
+mxEditor.prototype.installChangeHandler = function (graph)
+{
+ var listener = mxUtils.bind(this, function(sender, evt)
+ {
+ // Updates the modified state
+ this.setModified(true);
+
+ // Automatically validates the graph
+ // after each change
+ if (this.validating == true)
+ {
+ graph.validateGraph();
+ }
+
+ // Checks if the root has been changed
+ var changes = evt.getProperty('edit').changes;
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ if (change instanceof mxRootChange ||
+ (change instanceof mxValueChange &&
+ change.cell == this.graph.model.root) ||
+ (change instanceof mxCellAttributeChange &&
+ change.cell == this.graph.model.root))
+ {
+ this.fireEvent(new mxEventObject(mxEvent.ROOT));
+ break;
+ }
+ }
+ });
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+};
+
+/**
+ * Function: installInsertHandler
+ *
+ * Installs the handler for invoking <insertFunction> if
+ * one is defined.
+ */
+mxEditor.prototype.installInsertHandler = function (graph)
+{
+ var self = this; // closure
+ var insertHandler =
+ {
+ mouseDown: function(sender, me)
+ {
+ if (self.insertFunction != null &&
+ !me.isPopupTrigger() &&
+ (self.forcedInserting ||
+ me.getState() == null))
+ {
+ self.graph.clearSelection();
+ self.insertFunction(me.getEvent(), me.getCell());
+
+ // Consumes the rest of the events
+ // for this gesture (down, move, up)
+ this.isActive = true;
+ me.consume();
+ }
+ },
+
+ mouseMove: function(sender, me)
+ {
+ if (this.isActive)
+ {
+ me.consume();
+ }
+ },
+
+ mouseUp: function(sender, me)
+ {
+ if (this.isActive)
+ {
+ this.isActive = false;
+ me.consume();
+ }
+ }
+ };
+
+ graph.addMouseListener(insertHandler);
+};
+
+/**
+ * Function: createDiagramLayout
+ *
+ * Creates the layout instance used to layout the
+ * swimlanes in the diagram.
+ */
+mxEditor.prototype.createDiagramLayout = function ()
+{
+ var gs = this.graph.gridSize;
+ var layout = new mxStackLayout(this.graph, !this.horizontalFlow,
+ this.swimlaneSpacing, 2*gs, 2*gs);
+
+ // Overrides isIgnored to only take into account swimlanes
+ layout.isVertexIgnored = function(cell)
+ {
+ return !layout.graph.isSwimlane(cell);
+ };
+
+ return layout;
+};
+
+/**
+ * Function: createSwimlaneLayout
+ *
+ * Creates the layout instance used to layout the
+ * children of each swimlane.
+ */
+mxEditor.prototype.createSwimlaneLayout = function ()
+{
+ return new mxCompactTreeLayout(this.graph, this.horizontalFlow);
+};
+
+/**
+ * Function: createToolbar
+ *
+ * Creates the <toolbar> with no container.
+ */
+mxEditor.prototype.createToolbar = function ()
+{
+ return new mxDefaultToolbar(null, this);
+};
+
+/**
+ * Function: setToolbarContainer
+ *
+ * Initializes the toolbar for the given container.
+ */
+mxEditor.prototype.setToolbarContainer = function (container)
+{
+ this.toolbar.init(container);
+
+ // Workaround for stylesheet directives in IE
+ if (mxClient.IS_QUIRKS)
+ {
+ new mxDivResizer(container);
+ }
+};
+
+/**
+ * Function: setStatusContainer
+ *
+ * Creates the <status> using the specified container.
+ *
+ * This implementation adds listeners in the editor to
+ * display the last saved time and the current filename
+ * in the status bar.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the statusbar.
+ */
+mxEditor.prototype.setStatusContainer = function (container)
+{
+ if (this.status == null)
+ {
+ this.status = container;
+
+ // Prints the last saved time in the status bar
+ // when files are saved
+ this.addListener(mxEvent.SAVE, mxUtils.bind(this, function()
+ {
+ var tstamp = new Date().toLocaleString();
+ this.setStatus((mxResources.get(this.lastSavedResource) ||
+ this.lastSavedResource)+': '+tstamp);
+ }));
+
+ // Updates the statusbar to display the filename
+ // when new files are opened
+ this.addListener(mxEvent.OPEN, mxUtils.bind(this, function()
+ {
+ this.setStatus((mxResources.get(this.currentFileResource) ||
+ this.currentFileResource)+': '+this.filename);
+ }));
+
+ // Workaround for stylesheet directives in IE
+ if (mxClient.IS_QUIRKS)
+ {
+ new mxDivResizer(container);
+ }
+ }
+};
+
+/**
+ * Function: setStatus
+ *
+ * Display the specified message in the status bar.
+ *
+ * Parameters:
+ *
+ * message - String the specified the message to
+ * be displayed.
+ */
+mxEditor.prototype.setStatus = function (message)
+{
+ if (this.status != null && message != null)
+ {
+ this.status.innerHTML = message;
+ }
+};
+
+/**
+ * Function: setTitleContainer
+ *
+ * Creates a listener to update the inner HTML of the
+ * specified DOM node with the value of <getTitle>.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the title.
+ */
+mxEditor.prototype.setTitleContainer = function (container)
+{
+ this.addListener(mxEvent.ROOT, mxUtils.bind(this, function(sender)
+ {
+ container.innerHTML = this.getTitle();
+ }));
+
+ // Workaround for stylesheet directives in IE
+ if (mxClient.IS_QUIRKS)
+ {
+ new mxDivResizer(container);
+ }
+};
+
+/**
+ * Function: treeLayout
+ *
+ * Executes a vertical or horizontal compact tree layout
+ * using the specified cell as an argument. The cell may
+ * either be a group or the root of a tree.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to use in the compact tree layout.
+ * horizontal - Optional boolean to specify the tree's
+ * orientation. Default is true.
+ */
+mxEditor.prototype.treeLayout = function (cell, horizontal)
+{
+ if (cell != null)
+ {
+ var layout = new mxCompactTreeLayout(this.graph, horizontal);
+ layout.execute(cell);
+ }
+};
+
+/**
+ * Function: getTitle
+ *
+ * Returns the string value for the current root of the
+ * diagram.
+ */
+mxEditor.prototype.getTitle = function ()
+{
+ var title = '';
+ var graph = this.graph;
+ var cell = graph.getCurrentRoot();
+
+ while (cell != null &&
+ graph.getModel().getParent(
+ graph.getModel().getParent(cell)) != null)
+ {
+ // Append each label of a valid root
+ if (graph.isValidRoot(cell))
+ {
+ title = ' > ' +
+ graph.convertValueToString(cell) + title;
+ }
+
+ cell = graph.getModel().getParent(cell);
+ }
+
+ var prefix = this.getRootTitle();
+
+ return prefix + title;
+};
+
+/**
+ * Function: getRootTitle
+ *
+ * Returns the string value of the root cell in
+ * <mxGraph.model>.
+ */
+mxEditor.prototype.getRootTitle = function ()
+{
+ var root = this.graph.getModel().getRoot();
+ return this.graph.convertValueToString(root);
+};
+
+/**
+ * Function: undo
+ *
+ * Undo the last change in <graph>.
+ */
+mxEditor.prototype.undo = function ()
+{
+ this.undoManager.undo();
+};
+
+/**
+ * Function: redo
+ *
+ * Redo the last change in <graph>.
+ */
+mxEditor.prototype.redo = function ()
+{
+ this.undoManager.redo();
+};
+
+/**
+ * Function: groupCells
+ *
+ * Invokes <createGroup> to create a new group cell and the invokes
+ * <mxGraph.groupCells>, using the grid size of the graph as the spacing
+ * in the group's content area.
+ */
+mxEditor.prototype.groupCells = function ()
+{
+ var border = (this.groupBorderSize != null) ?
+ this.groupBorderSize :
+ this.graph.gridSize;
+ return this.graph.groupCells(this.createGroup(), border);
+};
+
+/**
+ * Function: createGroup
+ *
+ * Creates and returns a clone of <defaultGroup> to be used
+ * as a new group cell in <group>.
+ */
+mxEditor.prototype.createGroup = function ()
+{
+ var model = this.graph.getModel();
+
+ return model.cloneCell(this.defaultGroup);
+};
+
+/**
+ * Function: open
+ *
+ * Opens the specified file synchronously and parses it using
+ * <readGraphModel>. It updates <filename> and fires an <open>-event after
+ * the file has been opened. Exceptions should be handled as follows:
+ *
+ * (code)
+ * try
+ * {
+ * editor.open(filename);
+ * }
+ * catch (e)
+ * {
+ * mxUtils.error('Cannot open ' + filename +
+ * ': ' + e.message, 280, true);
+ * }
+ * (end)
+ *
+ * Parameters:
+ *
+ * filename - URL of the file to be opened.
+ */
+mxEditor.prototype.open = function (filename)
+{
+ if (filename != null)
+ {
+ var xml = mxUtils.load(filename).getXml();
+ this.readGraphModel(xml.documentElement);
+ this.filename = filename;
+
+ this.fireEvent(new mxEventObject(mxEvent.OPEN, 'filename', filename));
+ }
+};
+
+/**
+ * Function: readGraphModel
+ *
+ * Reads the specified XML node into the existing graph model and resets
+ * the command history and modified state.
+ */
+mxEditor.prototype.readGraphModel = function (node)
+{
+ var dec = new mxCodec(node.ownerDocument);
+ dec.decode(node, this.graph.getModel());
+ this.resetHistory();
+};
+
+/**
+ * Function: save
+ *
+ * Posts the string returned by <writeGraphModel> to the given URL or the
+ * URL returned by <getUrlPost>. The actual posting is carried out by
+ * <postDiagram>. If the URL is null then the resulting XML will be
+ * displayed using <mxUtils.popup>. Exceptions should be handled as
+ * follows:
+ *
+ * (code)
+ * try
+ * {
+ * editor.save();
+ * }
+ * catch (e)
+ * {
+ * mxUtils.error('Cannot save : ' + e.message, 280, true);
+ * }
+ * (end)
+ */
+mxEditor.prototype.save = function (url, linefeed)
+{
+ // Gets the URL to post the data to
+ url = url || this.getUrlPost();
+
+ // Posts the data if the URL is not empty
+ if (url != null && url.length > 0)
+ {
+ var data = this.writeGraphModel(linefeed);
+ this.postDiagram(url, data);
+
+ // Resets the modified flag
+ this.setModified(false);
+ }
+
+ // Dispatches a save event
+ this.fireEvent(new mxEventObject(mxEvent.SAVE, 'url', url));
+};
+
+/**
+ * Function: postDiagram
+ *
+ * Hook for subclassers to override the posting of a diagram
+ * represented by the given node to the given URL. This fires
+ * an asynchronous <post> event if the diagram has been posted.
+ *
+ * Example:
+ *
+ * To replace the diagram with the diagram in the response, use the
+ * following code.
+ *
+ * (code)
+ * editor.addListener(mxEvent.POST, function(sender, evt)
+ * {
+ * // Process response (replace diagram)
+ * var req = evt.getProperty('request');
+ * var root = req.getDocumentElement();
+ * editor.graph.readGraphModel(root)
+ * });
+ * (end)
+ */
+mxEditor.prototype.postDiagram = function (url, data)
+{
+ if (this.escapePostData)
+ {
+ data = encodeURIComponent(data);
+ }
+
+ mxUtils.post(url, this.postParameterName+'='+data,
+ mxUtils.bind(this, function(req)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.POST,
+ 'request', req, 'url', url, 'data', data));
+ })
+ );
+};
+
+/**
+ * Function: writeGraphModel
+ *
+ * Hook to create the string representation of the diagram. The default
+ * implementation uses an <mxCodec> to encode the graph model as
+ * follows:
+ *
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(this.graph.getModel());
+ * return mxUtils.getXml(node, this.linefeed);
+ * (end)
+ *
+ * Parameters:
+ *
+ * linefeed - Optional character to be used as the linefeed. Default is
+ * <linefeed>.
+ */
+mxEditor.prototype.writeGraphModel = function (linefeed)
+{
+ linefeed = (linefeed != null) ? linefeed : this.linefeed;
+ var enc = new mxCodec();
+ var node = enc.encode(this.graph.getModel());
+
+ return mxUtils.getXml(node, linefeed);
+};
+
+/**
+ * Function: getUrlPost
+ *
+ * Returns the URL to post the diagram to. This is used
+ * in <save>. The default implementation returns <urlPost>,
+ * adding <code>?draft=true</code>.
+ */
+mxEditor.prototype.getUrlPost = function ()
+{
+ return this.urlPost;
+};
+
+/**
+ * Function: getUrlImage
+ *
+ * Returns the URL to create the image with. This is typically
+ * the URL of a backend which accepts an XML representation
+ * of a graph view to create an image. The function is used
+ * in the image action to create an image. This implementation
+ * returns <urlImage>.
+ */
+mxEditor.prototype.getUrlImage = function ()
+{
+ return this.urlImage;
+};
+
+/**
+ * Function: connect
+ *
+ * Creates and returns a session for the specified parameters, installing
+ * the onChange function as a change listener for the session.
+ */
+mxEditor.prototype.connect = function (urlInit, urlPoll, urlNotify, onChange)
+{
+ var session = null;
+
+ if (!mxClient.IS_LOCAL)
+ {
+ session = new mxSession(this.graph.getModel(),
+ urlInit, urlPoll, urlNotify);
+
+ // Resets the undo history if the session was initialized which is the
+ // case if the message carries a namespace to be used for new IDs.
+ session.addListener(mxEvent.RECEIVE,
+ mxUtils.bind(this, function(sender, evt)
+ {
+ var node = evt.getProperty('node');
+
+ if (node.getAttribute('namespace') != null)
+ {
+ this.resetHistory();
+ }
+ })
+ );
+
+ // Installs the listener for all events
+ // that signal a change of the session
+ session.addListener(mxEvent.DISCONNECT, onChange);
+ session.addListener(mxEvent.CONNECT, onChange);
+ session.addListener(mxEvent.NOTIFY, onChange);
+ session.addListener(mxEvent.GET, onChange);
+ session.start();
+ }
+
+ return session;
+};
+
+/**
+ * Function: swapStyles
+ *
+ * Swaps the styles for the given names in the graph's
+ * stylesheet and refreshes the graph.
+ */
+mxEditor.prototype.swapStyles = function (first, second)
+{
+ var style = this.graph.getStylesheet().styles[second];
+ this.graph.getView().getStylesheet().putCellStyle(
+ second, this.graph.getStylesheet().styles[first]);
+ this.graph.getStylesheet().putCellStyle(first, style);
+ this.graph.refresh();
+};
+
+/**
+ * Function: showProperties
+ *
+ * Creates and shows the properties dialog for the given
+ * cell. The content area of the dialog is created using
+ * <createProperties>.
+ */
+mxEditor.prototype.showProperties = function (cell)
+{
+ cell = cell || this.graph.getSelectionCell();
+
+ // Uses the root node for the properties dialog
+ // if not cell was passed in and no cell is
+ // selected
+ if (cell == null)
+ {
+ cell = this.graph.getCurrentRoot();
+
+ if (cell == null)
+ {
+ cell = this.graph.getModel().getRoot();
+ }
+ }
+
+ if (cell != null)
+ {
+ // Makes sure there is no in-place editor in the
+ // graph and computes the location of the dialog
+ this.graph.stopEditing(true);
+
+ var offset = mxUtils.getOffset(this.graph.container);
+ var x = offset.x+10;
+ var y = offset.y;
+
+ // Avoids moving the dialog if it is alredy open
+ if (this.properties != null && !this.movePropertiesDialog)
+ {
+ x = this.properties.getX();
+ y = this.properties.getY();
+ }
+
+ // Places the dialog near the cell for which it
+ // displays the properties
+ else
+ {
+ var bounds = this.graph.getCellBounds(cell);
+
+ if (bounds != null)
+ {
+ x += bounds.x+Math.min(200, bounds.width);
+ y += bounds.y;
+ }
+ }
+
+ // Hides the existing properties dialog and creates a new one with the
+ // contents created in the hook method
+ this.hideProperties();
+ var node = this.createProperties(cell);
+
+ if (node != null)
+ {
+ // Displays the contents in a window and stores a reference to the
+ // window for later hiding of the window
+ this.properties = new mxWindow(mxResources.get(this.propertiesResource) ||
+ this.propertiesResource, node, x, y, this.propertiesWidth, this.propertiesHeight, false);
+ this.properties.setVisible(true);
+ }
+ }
+};
+
+/**
+ * Function: isPropertiesVisible
+ *
+ * Returns true if the properties dialog is currently visible.
+ */
+mxEditor.prototype.isPropertiesVisible = function ()
+{
+ return this.properties != null;
+};
+
+/**
+ * Function: createProperties
+ *
+ * Creates and returns the DOM node that represents the contents
+ * of the properties dialog for the given cell. This implementation
+ * works for user objects that are XML nodes and display all the
+ * node attributes in a form.
+ */
+mxEditor.prototype.createProperties = function (cell)
+{
+ var model = this.graph.getModel();
+ var value = model.getValue(cell);
+
+ if (mxUtils.isNode(value))
+ {
+ // Creates a form for the user object inside
+ // the cell
+ var form = new mxForm('properties');
+
+ // Adds a readonly field for the cell id
+ var id = form.addText('ID', cell.getId());
+ id.setAttribute('readonly', 'true');
+
+ var geo = null;
+ var yField = null;
+ var xField = null;
+ var widthField = null;
+ var heightField = null;
+
+ // Adds fields for the location and size
+ if (model.isVertex(cell))
+ {
+ geo = model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ yField = form.addText('top', geo.y);
+ xField = form.addText('left', geo.x);
+ widthField = form.addText('width', geo.width);
+ heightField = form.addText('height', geo.height);
+ }
+ }
+
+ // Adds a field for the cell style
+ var tmp = model.getStyle(cell);
+ var style = form.addText('Style', tmp || '');
+
+ // Creates textareas for each attribute of the
+ // user object within the cell
+ var attrs = value.attributes;
+ var texts = [];
+
+ for (var i = 0; i < attrs.length; i++)
+ {
+ // Creates a textarea with more lines for
+ // the cell label
+ var val = attrs[i].nodeValue;
+ texts[i] = form.addTextarea(attrs[i].nodeName, val,
+ (attrs[i].nodeName == 'label') ? 4 : 2);
+ }
+
+ // Adds an OK and Cancel button to the dialog
+ // contents and implements the respective
+ // actions below
+
+ // Defines the function to be executed when the
+ // OK button is pressed in the dialog
+ var okFunction = mxUtils.bind(this, function()
+ {
+ // Hides the dialog
+ this.hideProperties();
+
+ // Supports undo for the changes on the underlying
+ // XML structure / XML node attribute changes.
+ model.beginUpdate();
+ try
+ {
+ if (geo != null)
+ {
+ geo = geo.clone();
+
+ geo.x = parseFloat(xField.value);
+ geo.y = parseFloat(yField.value);
+ geo.width = parseFloat(widthField.value);
+ geo.height = parseFloat(heightField.value);
+
+ model.setGeometry(cell, geo);
+ }
+
+ // Applies the style
+ if (style.value.length > 0)
+ {
+ model.setStyle(cell, style.value);
+ }
+ else
+ {
+ model.setStyle(cell, null);
+ }
+
+ // Creates an undoable change for each
+ // attribute and executes it using the
+ // model, which will also make the change
+ // part of the current transaction
+ for (var i=0; i<attrs.length; i++)
+ {
+ var edit = new mxCellAttributeChange(
+ cell, attrs[i].nodeName,
+ texts[i].value);
+ model.execute(edit);
+ }
+
+ // Checks if the graph wants cells to
+ // be automatically sized and updates
+ // the size as an undoable step if
+ // the feature is enabled
+ if (this.graph.isAutoSizeCell(cell))
+ {
+ this.graph.updateCellSize(cell);
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ });
+
+ // Defines the function to be executed when the
+ // Cancel button is pressed in the dialog
+ var cancelFunction = mxUtils.bind(this, function()
+ {
+ // Hides the dialog
+ this.hideProperties();
+ });
+
+ form.addButtons(okFunction, cancelFunction);
+
+ return form.table;
+ }
+
+ return null;
+};
+
+/**
+ * Function: hideProperties
+ *
+ * Hides the properties dialog.
+ */
+mxEditor.prototype.hideProperties = function ()
+{
+ if (this.properties != null)
+ {
+ this.properties.destroy();
+ this.properties = null;
+ }
+};
+
+/**
+ * Function: showTasks
+ *
+ * Shows the tasks window. The tasks window is created using <createTasks>. The
+ * default width of the window is 200 pixels, the y-coordinate of the location
+ * can be specifies in <tasksTop> and the x-coordinate is right aligned with a
+ * 20 pixel offset from the right border. To change the location of the tasks
+ * window, the following code can be used:
+ *
+ * (code)
+ * var oldShowTasks = mxEditor.prototype.showTasks;
+ * mxEditor.prototype.showTasks = function()
+ * {
+ * oldShowTasks.apply(this, arguments); // "supercall"
+ *
+ * if (this.tasks != null)
+ * {
+ * this.tasks.setLocation(10, 10);
+ * }
+ * };
+ * (end)
+ */
+mxEditor.prototype.showTasks = function ()
+{
+ if (this.tasks == null)
+ {
+ var div = document.createElement('div');
+ div.style.padding = '4px';
+ div.style.paddingLeft = '20px';
+ var w = document.body.clientWidth;
+ var wnd = new mxWindow(
+ mxResources.get(this.tasksResource) ||
+ this.tasksResource,
+ div, w - 220, this.tasksTop, 200);
+ wnd.setClosable(true);
+ wnd.destroyOnClose = false;
+
+ // Installs a function to update the contents
+ // of the tasks window on every change of the
+ // model, selection or root.
+ var funct = mxUtils.bind(this, function(sender)
+ {
+ mxEvent.release(div);
+ div.innerHTML = '';
+ this.createTasks(div);
+ });
+
+ this.graph.getModel().addListener(mxEvent.CHANGE, funct);
+ this.graph.getSelectionModel().addListener(mxEvent.CHANGE, funct);
+ this.graph.addListener(mxEvent.ROOT, funct);
+
+ // Assigns the icon to the tasks window
+ if (this.tasksWindowImage != null)
+ {
+ wnd.setImage(this.tasksWindowImage);
+ }
+
+ this.tasks = wnd;
+ this.createTasks(div);
+ }
+
+ this.tasks.setVisible(true);
+};
+
+/**
+ * Function: refreshTasks
+ *
+ * Updates the contents of the tasks window using <createTasks>.
+ */
+mxEditor.prototype.refreshTasks = function (div)
+{
+ if (this.tasks != null)
+ {
+ var div = this.tasks.content;
+ mxEvent.release(div);
+ div.innerHTML = '';
+ this.createTasks(div);
+ }
+};
+
+/**
+ * Function: createTasks
+ *
+ * Updates the contents of the given DOM node to
+ * display the tasks associated with the current
+ * editor state. This is invoked whenever there
+ * is a possible change of state in the editor.
+ * Default implementation is empty.
+ */
+mxEditor.prototype.createTasks = function (div)
+{
+ // override
+};
+
+/**
+ * Function: showHelp
+ *
+ * Shows the help window. If the help window does not exist
+ * then it is created using an iframe pointing to the resource
+ * for the <code>urlHelp</code> key or <urlHelp> if the resource
+ * is undefined.
+ */
+mxEditor.prototype.showHelp = function (tasks)
+{
+ if (this.help == null)
+ {
+ var frame = document.createElement('iframe');
+ frame.setAttribute('src', mxResources.get('urlHelp') || this.urlHelp);
+ frame.setAttribute('height', '100%');
+ frame.setAttribute('width', '100%');
+ frame.setAttribute('frameBorder', '0');
+ frame.style.backgroundColor = 'white';
+
+ var w = document.body.clientWidth;
+ var h = (document.body.clientHeight || document.documentElement.clientHeight);
+
+ var wnd = new mxWindow(mxResources.get(this.helpResource) || this.helpResource,
+ frame, (w-this.helpWidth)/2, (h-this.helpHeight)/3, this.helpWidth, this.helpHeight);
+ wnd.setMaximizable(true);
+ wnd.setClosable(true);
+ wnd.destroyOnClose = false;
+ wnd.setResizable(true);
+
+ // Assigns the icon to the help window
+ if (this.helpWindowImage != null)
+ {
+ wnd.setImage(this.helpWindowImage);
+ }
+
+ // Workaround for ignored iframe height 100% in FF
+ if (mxClient.IS_NS)
+ {
+ var handler = function(sender)
+ {
+ var h = wnd.div.offsetHeight;
+ frame.setAttribute('height', (h-26)+'px');
+ };
+
+ wnd.addListener(mxEvent.RESIZE_END, handler);
+ wnd.addListener(mxEvent.MAXIMIZE, handler);
+ wnd.addListener(mxEvent.NORMALIZE, handler);
+ wnd.addListener(mxEvent.SHOW, handler);
+ }
+
+ this.help = wnd;
+ }
+
+ this.help.setVisible(true);
+};
+
+/**
+ * Function: showOutline
+ *
+ * Shows the outline window. If the window does not exist, then it is
+ * created using an <mxOutline>.
+ */
+mxEditor.prototype.showOutline = function ()
+{
+ var create = this.outline == null;
+
+ if (create)
+ {
+ var div = document.createElement('div');
+ div.style.overflow = 'hidden';
+ div.style.width = '100%';
+ div.style.height = '100%';
+ div.style.background = 'white';
+ div.style.cursor = 'move';
+
+ var wnd = new mxWindow(
+ mxResources.get(this.outlineResource) ||
+ this.outlineResource,
+ div, 600, 480, 200, 200, false);
+
+ // Creates the outline in the specified div
+ // and links it to the existing graph
+ var outline = new mxOutline(this.graph, div);
+ wnd.setClosable(true);
+ wnd.setResizable(true);
+ wnd.destroyOnClose = false;
+
+ wnd.addListener(mxEvent.RESIZE_END, function()
+ {
+ outline.update();
+ });
+
+ this.outline = wnd;
+ this.outline.outline = outline;
+ }
+
+ // Finally shows the outline
+ this.outline.setVisible(true);
+ this.outline.outline.update(true);
+};
+
+/**
+ * Function: setMode
+ *
+ * Puts the graph into the specified mode. The following modenames are
+ * supported:
+ *
+ * select - Selects using the left mouse button, new connections
+ * are disabled.
+ * connect - Selects using the left mouse button or creates new
+ * connections if mouse over cell hotspot. See <mxConnectionHandler>.
+ * pan - Pans using the left mouse button, new connections are disabled.
+ */
+mxEditor.prototype.setMode = function(modename)
+{
+ if (modename == 'select')
+ {
+ this.graph.panningHandler.useLeftButtonForPanning = false;
+ this.graph.setConnectable(false);
+ }
+ else if (modename == 'connect')
+ {
+ this.graph.panningHandler.useLeftButtonForPanning = false;
+ this.graph.setConnectable(true);
+ }
+ else if (modename == 'pan')
+ {
+ this.graph.panningHandler.useLeftButtonForPanning = true;
+ this.graph.setConnectable(false);
+ }
+};
+
+/**
+ * Function: createPopupMenu
+ *
+ * Uses <popupHandler> to create the menu in the graph's
+ * panning handler. The redirection is setup in
+ * <setToolbarContainer>.
+ */
+mxEditor.prototype.createPopupMenu = function (menu, cell, evt)
+{
+ this.popupHandler.createMenu(this, menu, cell, evt);
+};
+
+/**
+ * Function: createEdge
+ *
+ * Uses <defaultEdge> as the prototype for creating new edges
+ * in the connection handler of the graph. The style of the
+ * edge will be overridden with the value returned by
+ * <getEdgeStyle>.
+ */
+mxEditor.prototype.createEdge = function (source, target)
+{
+ // Clones the defaultedge prototype
+ var e = null;
+
+ if (this.defaultEdge != null)
+ {
+ var model = this.graph.getModel();
+ e = model.cloneCell(this.defaultEdge);
+ }
+ else
+ {
+ e = new mxCell('');
+ e.setEdge(true);
+
+ var geo = new mxGeometry();
+ geo.relative = true;
+ e.setGeometry(geo);
+ }
+
+ // Overrides the edge style
+ var style = this.getEdgeStyle();
+
+ if (style != null)
+ {
+ e.setStyle(style);
+ }
+
+ return e;
+};
+
+/**
+ * Function: getEdgeStyle
+ *
+ * Returns a string identifying the style of new edges.
+ * The function is used in <createEdge> when new edges
+ * are created in the graph.
+ */
+mxEditor.prototype.getEdgeStyle = function ()
+{
+ return this.defaultEdgeStyle;
+};
+
+/**
+ * Function: consumeCycleAttribute
+ *
+ * Returns the next attribute in <cycleAttributeValues>
+ * or null, if not attribute should be used in the
+ * specified cell.
+ */
+mxEditor.prototype.consumeCycleAttribute = function (cell)
+{
+ return (this.cycleAttributeValues != null &&
+ this.cycleAttributeValues.length > 0 &&
+ this.graph.isSwimlane(cell)) ?
+ this.cycleAttributeValues[this.cycleAttributeIndex++ %
+ this.cycleAttributeValues.length] : null;
+};
+
+/**
+ * Function: cycleAttribute
+ *
+ * Uses the returned value from <consumeCycleAttribute>
+ * as the value for the <cycleAttributeName> key in
+ * the given cell's style.
+ */
+mxEditor.prototype.cycleAttribute = function (cell)
+{
+ if (this.cycleAttributeName != null)
+ {
+ var value = this.consumeCycleAttribute(cell);
+
+ if (value != null)
+ {
+ cell.setStyle(cell.getStyle()+';'+
+ this.cycleAttributeName+'='+value);
+ }
+ }
+};
+
+/**
+ * Function: addVertex
+ *
+ * Adds the given vertex as a child of parent at the specified
+ * x and y coordinate and fires an <addVertex> event.
+ */
+mxEditor.prototype.addVertex = function (parent, vertex, x, y)
+{
+ var model = this.graph.getModel();
+
+ while (parent != null && !this.graph.isValidDropTarget(parent))
+ {
+ parent = model.getParent(parent);
+ }
+
+ parent = (parent != null) ? parent : this.graph.getSwimlaneAt(x, y);
+ var scale = this.graph.getView().scale;
+
+ var geo = model.getGeometry(vertex);
+ var pgeo = model.getGeometry(parent);
+
+ if (this.graph.isSwimlane(vertex) &&
+ !this.graph.swimlaneNesting)
+ {
+ parent = null;
+ }
+ else if (parent == null && this.swimlaneRequired)
+ {
+ return null;
+ }
+ else if (parent != null && pgeo != null)
+ {
+ // Keeps vertex inside parent
+ var state = this.graph.getView().getState(parent);
+
+ if (state != null)
+ {
+ x -= state.origin.x * scale;
+ y -= state.origin.y * scale;
+
+ if (this.graph.isConstrainedMoving)
+ {
+ var width = geo.width;
+ var height = geo.height;
+ var tmp = state.x+state.width;
+
+ if (x+width > tmp)
+ {
+ x -= x+width - tmp;
+ }
+
+ tmp = state.y+state.height;
+
+ if (y+height > tmp)
+ {
+ y -= y+height - tmp;
+ }
+ }
+ }
+ else if (pgeo != null)
+ {
+ x -= pgeo.x*scale;
+ y -= pgeo.y*scale;
+ }
+ }
+
+ geo = geo.clone();
+ geo.x = this.graph.snap(x / scale -
+ this.graph.getView().translate.x -
+ this.graph.gridSize/2);
+ geo.y = this.graph.snap(y / scale -
+ this.graph.getView().translate.y -
+ this.graph.gridSize/2);
+ vertex.setGeometry(geo);
+
+ if (parent == null)
+ {
+ parent = this.graph.getDefaultParent();
+ }
+
+ this.cycleAttribute(vertex);
+ this.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX,
+ 'vertex', vertex, 'parent', parent));
+
+ model.beginUpdate();
+ try
+ {
+ vertex = this.graph.addCell(vertex, parent);
+
+ if (vertex != null)
+ {
+ this.graph.constrainChild(vertex);
+
+ this.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX, 'vertex', vertex));
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+
+ if (vertex != null)
+ {
+ this.graph.setSelectionCell(vertex);
+ this.graph.scrollCellToVisible(vertex);
+ this.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX, 'vertex', vertex));
+ }
+
+ return vertex;
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes the editor and all its associated resources. This does not
+ * normally need to be called, it is called automatically when the window
+ * unloads.
+ */
+mxEditor.prototype.destroy = function ()
+{
+ if (!this.destroyed)
+ {
+ this.destroyed = true;
+
+ if (this.tasks != null)
+ {
+ this.tasks.destroy();
+ }
+
+ if (this.outline != null)
+ {
+ this.outline.destroy();
+ }
+
+ if (this.properties != null)
+ {
+ this.properties.destroy();
+ }
+
+ if (this.keyHandler != null)
+ {
+ this.keyHandler.destroy();
+ }
+
+ if (this.rubberband != null)
+ {
+ this.rubberband.destroy();
+ }
+
+ if (this.toolbar != null)
+ {
+ this.toolbar.destroy();
+ }
+
+ if (this.graph != null)
+ {
+ this.graph.destroy();
+ }
+
+ this.status = null;
+ this.templates = null;
+ }
+};
diff --git a/src/js/handler/mxCellHighlight.js b/src/js/handler/mxCellHighlight.js
new file mode 100644
index 0000000..f967f00
--- /dev/null
+++ b/src/js/handler/mxCellHighlight.js
@@ -0,0 +1,271 @@
+/**
+ * $Id: mxCellHighlight.js,v 1.25 2012-09-27 14:43:40 boris Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellHighlight
+ *
+ * A helper class to highlight cells. Here is an example for a given cell.
+ *
+ * (code)
+ * var highlight = new mxCellHighlight(graph, '#ff0000', 2);
+ * highlight.highlight(graph.view.getState(cell)));
+ * (end)
+ *
+ * Constructor: mxCellHighlight
+ *
+ * Constructs a cell highlight.
+ */
+function mxCellHighlight(graph, highlightColor, strokeWidth)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.highlightColor = (highlightColor != null) ? highlightColor : mxConstants.DEFAULT_VALID_COLOR;
+ this.strokeWidth = (strokeWidth != null) ? strokeWidth : mxConstants.HIGHLIGHT_STROKEWIDTH;
+
+ // Updates the marker if the graph changes
+ this.repaintHandler = mxUtils.bind(this, function()
+ {
+ this.repaint();
+ });
+
+ this.graph.getView().addListener(mxEvent.SCALE, this.repaintHandler);
+ this.graph.getView().addListener(mxEvent.TRANSLATE, this.repaintHandler);
+ this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler);
+ this.graph.getModel().addListener(mxEvent.CHANGE, this.repaintHandler);
+
+ // Hides the marker if the current root changes
+ this.resetHandler = mxUtils.bind(this, function()
+ {
+ this.hide();
+ });
+
+ this.graph.getView().addListener(mxEvent.DOWN, this.resetHandler);
+ this.graph.getView().addListener(mxEvent.UP, this.resetHandler);
+ }
+};
+
+/**
+ * Variable: keepOnTop
+ *
+ * Specifies if the highlights should appear on top of everything
+ * else in the overlay pane. Default is false.
+ */
+mxCellHighlight.prototype.keepOnTop = false;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellHighlight.prototype.graph = true;
+
+/**
+ * Variable: state
+ *
+ * Reference to the <mxCellState>.
+ */
+mxCellHighlight.prototype.state = null;
+
+/**
+ * Variable: spacing
+ *
+ * Specifies the spacing between the highlight for vertices and the vertex.
+ * Default is 2.
+ */
+mxCellHighlight.prototype.spacing = 2;
+
+/**
+ * Variable: resetHandler
+ *
+ * Holds the handler that automatically invokes reset if the highlight
+ * should be hidden.
+ */
+mxCellHighlight.prototype.resetHandler = null;
+
+/**
+ * Function: setHighlightColor
+ *
+ * Sets the color of the rectangle used to highlight drop targets.
+ *
+ * Parameters:
+ *
+ * color - String that represents the new highlight color.
+ */
+mxCellHighlight.prototype.setHighlightColor = function(color)
+{
+ this.highlightColor = color;
+
+ if (this.shape != null)
+ {
+ if (this.shape.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.shape.innerNode.setAttribute('stroke', color);
+ }
+ else if (this.shape.dialect == mxConstants.DIALECT_VML)
+ {
+ this.shape.node.strokecolor = color;
+ }
+ }
+};
+
+/**
+ * Function: drawHighlight
+ *
+ * Creates and returns the highlight shape for the given state.
+ */
+mxCellHighlight.prototype.drawHighlight = function()
+{
+ this.shape = this.createShape();
+ this.repaint();
+
+ if (!this.keepOnTop && this.shape.node.parentNode.firstChild != this.shape.node)
+ {
+ this.shape.node.parentNode.insertBefore(this.shape.node, this.shape.node.parentNode.firstChild);
+ }
+
+ // Workaround to force a repaint in AppleWebKit
+ if (this.graph.model.isEdge(this.state.cell))
+ {
+ mxUtils.repaintGraph(this.graph, this.shape.points[0]);
+ }
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates and returns the highlight shape for the given state.
+ */
+mxCellHighlight.prototype.createShape = function()
+{
+ var shape = null;
+
+ if (this.graph.model.isEdge(this.state.cell))
+ {
+ shape = new mxPolyline(this.state.absolutePoints,
+ this.highlightColor, this.strokeWidth);
+ }
+ else
+ {
+ shape = new mxRectangleShape( new mxRectangle(),
+ null, this.highlightColor, this.strokeWidth);
+ }
+
+ shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ shape.init(this.graph.getView().getOverlayPane());
+ mxEvent.redirectMouseEvents(shape.node, this.graph, this.state);
+
+ return shape;
+};
+
+
+/**
+ * Function: repaint
+ *
+ * Updates the highlight after a change of the model or view.
+ */
+mxCellHighlight.prototype.repaint = function()
+{
+ if (this.state != null && this.shape != null)
+ {
+ if (this.graph.model.isEdge(this.state.cell))
+ {
+ this.shape.points = this.state.absolutePoints;
+ }
+ else
+ {
+ this.shape.bounds = new mxRectangle(this.state.x - this.spacing, this.state.y - this.spacing,
+ this.state.width + 2 * this.spacing, this.state.height + 2 * this.spacing);
+ }
+
+ // Uses cursor from shape in highlight
+ if (this.state.shape != null)
+ {
+ this.shape.setCursor(this.state.shape.getCursor());
+ }
+
+ var alpha = (!this.graph.model.isEdge(this.state.cell)) ? Number(this.state.style[mxConstants.STYLE_ROTATION] || '0') : 0;
+
+ // Event-transparency
+ if (this.shape.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.shape.node.setAttribute('style', 'pointer-events:none;');
+
+ if (alpha != 0)
+ {
+ var cx = this.state.getCenterX();
+ var cy = this.state.getCenterY();
+ var transform = 'rotate(' + alpha + ' ' + cx + ' ' + cy + ')';
+
+ this.shape.node.setAttribute('transform', transform);
+ }
+ }
+ else
+ {
+ this.shape.node.style.background = '';
+
+ if (alpha != 0)
+ {
+ this.shape.node.rotation = alpha;
+ }
+ }
+
+ this.shape.redraw();
+ }
+};
+
+/**
+ * Function: hide
+ *
+ * Resets the state of the cell marker.
+ */
+mxCellHighlight.prototype.hide = function()
+{
+ this.highlight(null);
+};
+
+/**
+ * Function: mark
+ *
+ * Marks the <markedState> and fires a <mark> event.
+ */
+mxCellHighlight.prototype.highlight = function(state)
+{
+ if (this.state != state)
+ {
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+
+ this.state = state;
+
+ if (this.state != null)
+ {
+ this.drawHighlight();
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxCellHighlight.prototype.destroy = function()
+{
+ this.graph.getView().removeListener(this.repaintHandler);
+ this.graph.getModel().removeListener(this.repaintHandler);
+
+ this.graph.getView().removeListener(this.resetHandler);
+ this.graph.getModel().removeListener(this.resetHandler);
+
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+};
diff --git a/src/js/handler/mxCellMarker.js b/src/js/handler/mxCellMarker.js
new file mode 100644
index 0000000..b336278
--- /dev/null
+++ b/src/js/handler/mxCellMarker.js
@@ -0,0 +1,419 @@
+/**
+ * $Id: mxCellMarker.js,v 1.30 2011-07-15 12:57:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellMarker
+ *
+ * A helper class to process mouse locations and highlight cells.
+ *
+ * Helper class to highlight cells. To add a cell marker to an existing graph
+ * for highlighting all cells, the following code is used:
+ *
+ * (code)
+ * var marker = new mxCellMarker(graph);
+ * graph.addMouseListener({
+ * mouseDown: function() {},
+ * mouseMove: function(sender, me)
+ * {
+ * marker.process(me);
+ * },
+ * mouseUp: function() {}
+ * });
+ * (end)
+ *
+ * Event: mxEvent.MARK
+ *
+ * Fires after a cell has been marked or unmarked. The <code>state</code>
+ * property contains the marked <mxCellState> or null if no state is marked.
+ *
+ * Constructor: mxCellMarker
+ *
+ * Constructs a new cell marker.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * validColor - Optional marker color for valid states. Default is
+ * <mxConstants.DEFAULT_VALID_COLOR>.
+ * invalidColor - Optional marker color for invalid states. Default is
+ * <mxConstants.DEFAULT_INVALID_COLOR>.
+ * hotspot - Portion of the width and hight where a state intersects a
+ * given coordinate pair. A value of 0 means always highlight. Default is
+ * <mxConstants.DEFAULT_HOTSPOT>.
+ */
+function mxCellMarker(graph, validColor, invalidColor, hotspot)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.validColor = (validColor != null) ? validColor : mxConstants.DEFAULT_VALID_COLOR;
+ this.invalidColor = (validColor != null) ? invalidColor : mxConstants.DEFAULT_INVALID_COLOR;
+ this.hotspot = (hotspot != null) ? hotspot : mxConstants.DEFAULT_HOTSPOT;
+
+ this.highlight = new mxCellHighlight(graph);
+ }
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxCellMarker.prototype = new mxEventSource();
+mxCellMarker.prototype.constructor = mxCellMarker;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellMarker.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if the marker is enabled. Default is true.
+ */
+mxCellMarker.prototype.enabled = true;
+
+/**
+ * Variable: hotspot
+ *
+ * Specifies the portion of the width and height that should trigger
+ * a highlight. The area around the center of the cell to be marked is used
+ * as the hotspot. Possible values are between 0 and 1. Default is
+ * mxConstants.DEFAULT_HOTSPOT.
+ */
+mxCellMarker.prototype.hotspot = mxConstants.DEFAULT_HOTSPOT;
+
+/**
+ * Variable: hotspotEnabled
+ *
+ * Specifies if the hotspot is enabled. Default is false.
+ */
+mxCellMarker.prototype.hotspotEnabled = false;
+
+/**
+ * Variable: validColor
+ *
+ * Holds the valid marker color.
+ */
+mxCellMarker.prototype.validColor = null;
+
+/**
+ * Variable: invalidColor
+ *
+ * Holds the invalid marker color.
+ */
+mxCellMarker.prototype.invalidColor = null;
+
+/**
+ * Variable: currentColor
+ *
+ * Holds the current marker color.
+ */
+mxCellMarker.prototype.currentColor = null;
+
+/**
+ * Variable: validState
+ *
+ * Holds the marked <mxCellState> if it is valid.
+ */
+mxCellMarker.prototype.validState = null;
+
+/**
+ * Variable: markedState
+ *
+ * Holds the marked <mxCellState>.
+ */
+mxCellMarker.prototype.markedState = null;
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxCellMarker.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxCellMarker.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setHotspot
+ *
+ * Sets the <hotspot>.
+ */
+mxCellMarker.prototype.setHotspot = function(hotspot)
+{
+ this.hotspot = hotspot;
+};
+
+/**
+ * Function: getHotspot
+ *
+ * Returns the <hotspot>.
+ */
+mxCellMarker.prototype.getHotspot = function()
+{
+ return this.hotspot;
+};
+
+/**
+ * Function: setHotspotEnabled
+ *
+ * Specifies whether the hotspot should be used in <intersects>.
+ */
+mxCellMarker.prototype.setHotspotEnabled = function(enabled)
+{
+ this.hotspotEnabled = enabled;
+};
+
+/**
+ * Function: isHotspotEnabled
+ *
+ * Returns true if hotspot is used in <intersects>.
+ */
+mxCellMarker.prototype.isHotspotEnabled = function()
+{
+ return this.hotspotEnabled;
+};
+
+/**
+ * Function: hasValidState
+ *
+ * Returns true if <validState> is not null.
+ */
+mxCellMarker.prototype.hasValidState = function()
+{
+ return this.validState != null;
+};
+
+/**
+ * Function: getValidState
+ *
+ * Returns the <validState>.
+ */
+mxCellMarker.prototype.getValidState = function()
+{
+ return this.validState;
+};
+
+/**
+ * Function: getMarkedState
+ *
+ * Returns the <markedState>.
+ */
+mxCellMarker.prototype.getMarkedState = function()
+{
+ return this.markedState;
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of the cell marker.
+ */
+mxCellMarker.prototype.reset = function()
+{
+ this.validState = null;
+
+ if (this.markedState != null)
+ {
+ this.markedState = null;
+ this.unmark();
+ }
+};
+
+/**
+ * Function: process
+ *
+ * Processes the given event and cell and marks the state returned by
+ * <getState> with the color returned by <getMarkerColor>. If the
+ * markerColor is not null, then the state is stored in <markedState>. If
+ * <isValidState> returns true, then the state is stored in <validState>
+ * regardless of the marker color. The state is returned regardless of the
+ * marker color and valid state.
+ */
+mxCellMarker.prototype.process = function(me)
+{
+ var state = null;
+
+ if (this.isEnabled())
+ {
+ state = this.getState(me);
+ var isValid = (state != null) ? this.isValidState(state) : false;
+ var color = this.getMarkerColor(me.getEvent(), state, isValid);
+
+ if (isValid)
+ {
+ this.validState = state;
+ }
+ else
+ {
+ this.validState = null;
+ }
+
+ if (state != this.markedState || color != this.currentColor)
+ {
+ this.currentColor = color;
+
+ if (state != null && this.currentColor != null)
+ {
+ this.markedState = state;
+ this.mark();
+ }
+ else if (this.markedState != null)
+ {
+ this.markedState = null;
+ this.unmark();
+ }
+ }
+ }
+
+ return state;
+};
+
+/**
+ * Function: markCell
+ *
+ * Marks the given cell using the given color, or <validColor> if no color is specified.
+ */
+mxCellMarker.prototype.markCell = function(cell, color)
+{
+ var state = this.graph.getView().getState(cell);
+
+ if (state != null)
+ {
+ this.currentColor = (color != null) ? color : this.validColor;
+ this.markedState = state;
+ this.mark();
+ }
+};
+
+/**
+ * Function: mark
+ *
+ * Marks the <markedState> and fires a <mark> event.
+ */
+mxCellMarker.prototype.mark = function()
+{
+ this.highlight.setHighlightColor(this.currentColor);
+ this.highlight.highlight(this.markedState);
+ this.fireEvent(new mxEventObject(mxEvent.MARK, 'state', this.markedState));
+};
+
+/**
+ * Function: unmark
+ *
+ * Hides the marker and fires a <mark> event.
+ */
+mxCellMarker.prototype.unmark = function()
+{
+ this.mark();
+};
+
+/**
+ * Function: isValidState
+ *
+ * Returns true if the given <mxCellState> is a valid state. If this
+ * returns true, then the state is stored in <validState>. The return value
+ * of this method is used as the argument for <getMarkerColor>.
+ */
+mxCellMarker.prototype.isValidState = function(state)
+{
+ return true;
+};
+
+/**
+ * Function: getMarkerColor
+ *
+ * Returns the valid- or invalidColor depending on the value of isValid.
+ * The given <mxCellState> is ignored by this implementation.
+ */
+mxCellMarker.prototype.getMarkerColor = function(evt, state, isValid)
+{
+ return (isValid) ? this.validColor : this.invalidColor;
+};
+
+/**
+ * Function: getState
+ *
+ * Uses <getCell>, <getStateToMark> and <intersects> to return the
+ * <mxCellState> for the given <mxMouseEvent>.
+ */
+mxCellMarker.prototype.getState = function(me)
+{
+ var view = this.graph.getView();
+ cell = this.getCell(me);
+ var state = this.getStateToMark(view.getState(cell));
+
+ return (state != null && this.intersects(state, me)) ? state : null;
+};
+
+/**
+ * Function: getCell
+ *
+ * Returns the <mxCell> for the given event and cell. This returns the
+ * given cell.
+ */
+mxCellMarker.prototype.getCell = function(me)
+{
+ return me.getCell();
+};
+
+/**
+ * Function: getStateToMark
+ *
+ * Returns the <mxCellState> to be marked for the given <mxCellState> under
+ * the mouse. This returns the given state.
+ */
+mxCellMarker.prototype.getStateToMark = function(state)
+{
+ return state;
+};
+
+/**
+ * Function: intersects
+ *
+ * Returns true if the given coordinate pair intersects the given state.
+ * This returns true if the <hotspot> is 0 or the coordinates are inside
+ * the hotspot for the given cell state.
+ */
+mxCellMarker.prototype.intersects = function(state, me)
+{
+ if (this.hotspotEnabled)
+ {
+ return mxUtils.intersectsHotspot(state, me.getGraphX(), me.getGraphY(),
+ this.hotspot, mxConstants.MIN_HOTSPOT_SIZE,
+ mxConstants.MAX_HOTSPOT_SIZE);
+ }
+
+ return true;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxCellMarker.prototype.destroy = function()
+{
+ this.graph.getView().removeListener(this.resetHandler);
+ this.graph.getModel().removeListener(this.resetHandler);
+ this.highlight.destroy();
+};
diff --git a/src/js/handler/mxCellTracker.js b/src/js/handler/mxCellTracker.js
new file mode 100644
index 0000000..5adcd6a
--- /dev/null
+++ b/src/js/handler/mxCellTracker.js
@@ -0,0 +1,149 @@
+/**
+ * $Id: mxCellTracker.js,v 1.9 2011-08-28 09:49:46 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellTracker
+ *
+ * Event handler that highlights cells. Inherits from <mxCellMarker>.
+ *
+ * Example:
+ *
+ * (code)
+ * new mxCellTracker(graph, '#00FF00');
+ * (end)
+ *
+ * For detecting dragEnter, dragOver and dragLeave on cells, the following
+ * code can be used:
+ *
+ * (code)
+ * graph.addMouseListener(
+ * {
+ * cell: null,
+ * mouseDown: function(sender, me) { },
+ * mouseMove: function(sender, me)
+ * {
+ * var tmp = me.getCell();
+ *
+ * if (tmp != this.cell)
+ * {
+ * if (this.cell != null)
+ * {
+ * this.dragLeave(me.getEvent(), this.cell);
+ * }
+ *
+ * this.cell = tmp;
+ *
+ * if (this.cell != null)
+ * {
+ * this.dragEnter(me.getEvent(), this.cell);
+ * }
+ * }
+ *
+ * if (this.cell != null)
+ * {
+ * this.dragOver(me.getEvent(), this.cell);
+ * }
+ * },
+ * mouseUp: function(sender, me) { },
+ * dragEnter: function(evt, cell)
+ * {
+ * mxLog.debug('dragEnter', cell.value);
+ * },
+ * dragOver: function(evt, cell)
+ * {
+ * mxLog.debug('dragOver', cell.value);
+ * },
+ * dragLeave: function(evt, cell)
+ * {
+ * mxLog.debug('dragLeave', cell.value);
+ * }
+ * });
+ * (end)
+ *
+ * Constructor: mxCellTracker
+ *
+ * Constructs an event handler that highlights cells.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * color - Color of the highlight. Default is blue.
+ * funct - Optional JavaScript function that is used to override
+ * <mxCellMarker.getCell>.
+ */
+function mxCellTracker(graph, color, funct)
+{
+ mxCellMarker.call(this, graph, color);
+
+ this.graph.addMouseListener(this);
+
+ if (funct != null)
+ {
+ this.getCell = funct;
+ }
+
+ // Automatic deallocation of memory
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+ {
+ this.destroy();
+ }));
+ }
+};
+
+/**
+ * Extends mxCellMarker.
+ */
+mxCellTracker.prototype = new mxCellMarker();
+mxCellTracker.prototype.constructor = mxCellTracker;
+
+/**
+ * Function: mouseDown
+ *
+ * Ignores the event. The event is not consumed.
+ */
+mxCellTracker.prototype.mouseDown = function(sender, me) { };
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by highlighting the cell under the mousepointer if it
+ * is over the hotspot region of the cell.
+ */
+mxCellTracker.prototype.mouseMove = function(sender, me)
+{
+ if (this.isEnabled())
+ {
+ this.process(me);
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by reseting the highlight.
+ */
+mxCellTracker.prototype.mouseUp = function(sender, me)
+{
+ this.reset();
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the object and all its resources and DOM nodes. This doesn't
+ * normally need to be called. It is called automatically when the window
+ * unloads.
+ */
+mxCellTracker.prototype.destroy = function()
+{
+ if (!this.destroyed)
+ {
+ this.destroyed = true;
+
+ this.graph.removeMouseListener(this);
+ mxCellMarker.prototype.destroy.apply(this);
+ }
+};
diff --git a/src/js/handler/mxConnectionHandler.js b/src/js/handler/mxConnectionHandler.js
new file mode 100644
index 0000000..07daaf8
--- /dev/null
+++ b/src/js/handler/mxConnectionHandler.js
@@ -0,0 +1,1969 @@
+/**
+ * $Id: mxConnectionHandler.js,v 1.216 2012-12-07 15:17:37 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxConnectionHandler
+ *
+ * Graph event handler that creates new connections. Uses <mxTerminalMarker>
+ * for finding and highlighting the source and target vertices and
+ * <factoryMethod> to create the edge instance. This handler is built-into
+ * <mxGraph.connectionHandler> and enabled using <mxGraph.setConnectable>.
+ *
+ * Example:
+ *
+ * (code)
+ * new mxConnectionHandler(graph, function(source, target, style)
+ * {
+ * edge = new mxCell('', new mxGeometry());
+ * edge.setEdge(true);
+ * edge.setStyle(style);
+ * edge.geometry.relative = true;
+ * return edge;
+ * });
+ * (end)
+ *
+ * Here is an alternative solution that just sets a specific user object for
+ * new edges by overriding <insertEdge>.
+ *
+ * (code)
+ * mxConnectionHandlerInsertEdge = mxConnectionHandler.prototype.insertEdge;
+ * mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
+ * {
+ * value = 'Test';
+ *
+ * return mxConnectionHandlerInsertEdge.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Using images to trigger connections:
+ *
+ * This handler uses mxTerminalMarker to find the source and target cell for
+ * the new connection and creates a new edge using <connect>. The new edge is
+ * created using <createEdge> which in turn uses <factoryMethod> or creates a
+ * new default edge.
+ *
+ * The handler uses a "highlight-paradigm" for indicating if a cell is being
+ * used as a source or target terminal, as seen in MS Visio and other products.
+ * In order to allow both, moving and connecting cells at the same time,
+ * <mxConstants.DEFAULT_HOTSPOT> is used in the handler to determine the hotspot
+ * of a cell, that is, the region of the cell which is used to trigger a new
+ * connection. The constant is a value between 0 and 1 that specifies the
+ * amount of the width and height around the center to be used for the hotspot
+ * of a cell and its default value is 0.5. In addition,
+ * <mxConstants.MIN_HOTSPOT_SIZE> defines the minimum number of pixels for the
+ * width and height of the hotspot.
+ *
+ * This solution, while standards compliant, may be somewhat confusing because
+ * there is no visual indicator for the hotspot and the highlight is seen to
+ * switch on and off while the mouse is being moved in and out. Furthermore,
+ * this paradigm does not allow to create different connections depending on
+ * the highlighted hotspot as there is only one hotspot per cell and it
+ * normally does not allow cells to be moved and connected at the same time as
+ * there is no clear indication of the connectable area of the cell.
+ *
+ * To come across these issues, the handle has an additional <createIcons> hook
+ * with a default implementation that allows to create one icon to be used to
+ * trigger new connections. If this icon is specified, then new connections can
+ * only be created if the image is clicked while the cell is being highlighted.
+ * The <createIcons> hook may be overridden to create more than one
+ * <mxImageShape> for creating new connections, but the default implementation
+ * supports one image and is used as follows:
+ *
+ * In order to display the "connect image" whenever the mouse is over the cell,
+ * an DEFAULT_HOTSPOT of 1 should be used:
+ *
+ * (code)
+ * mxConstants.DEFAULT_HOTSPOT = 1;
+ * (end)
+ *
+ * In order to avoid confusion with the highlighting, the highlight color
+ * should not be used with a connect image:
+ *
+ * (code)
+ * mxConstants.HIGHLIGHT_COLOR = null;
+ * (end)
+ *
+ * To install the image, the connectImage field of the mxConnectionHandler must
+ * be assigned a new <mxImage> instance:
+ *
+ * (code)
+ * mxConnectionHandler.prototype.connectImage = new mxImage('images/green-dot.gif', 14, 14);
+ * (end)
+ *
+ * This will use the green-dot.gif with a width and height of 14 pixels as the
+ * image to trigger new connections. In createIcons the icon field of the
+ * handler will be set in order to remember the icon that has been clicked for
+ * creating the new connection. This field will be available under selectedIcon
+ * in the connect method, which may be overridden to take the icon that
+ * triggered the new connection into account. This is useful if more than one
+ * icon may be used to create a connection.
+ *
+ * Group: Events
+ *
+ * Event: mxEvent.START
+ *
+ * Fires when a new connection is being created by the user. The <code>state</code>
+ * property contains the state of the source cell.
+ *
+ * Event: mxEvent.CONNECT
+ *
+ * Fires between begin- and endUpdate in <connect>. The <code>cell</code>
+ * property contains the inserted edge, the <code>event</code> and <code>target</code>
+ * properties contain the respective arguments that were passed to <connect> (where
+ * target corresponds to the dropTarget argument).
+ *
+ * Note that the target is the cell under the mouse where the mouse button was released.
+ * Depending on the logic in the handler, this doesn't necessarily have to be the target
+ * of the inserted edge. To print the source, target or any optional ports IDs that the
+ * edge is connected to, the following code can be used. To get more details about the
+ * actual connection point, <mxGraph.getConnectionConstraint> can be used. To resolve
+ * the port IDs, use <mxGraphModel.getCell>.
+ *
+ * (code)
+ * graph.connectionHandler.addListener(mxEvent.CONNECT, function(sender, evt)
+ * {
+ * var edge = evt.getProperty('cell');
+ * var source = graph.getModel().getTerminal(edge, true);
+ * var target = graph.getModel().getTerminal(edge, false);
+ *
+ * var style = graph.getCellStyle(edge);
+ * var sourcePortId = style[mxConstants.STYLE_SOURCE_PORT];
+ * var targetPortId = style[mxConstants.STYLE_TARGET_PORT];
+ *
+ * mxLog.show();
+ * mxLog.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId);
+ * });
+ * (end)
+ *
+ * Event: mxEvent.RESET
+ *
+ * Fires when the <reset> method is invoked.
+ *
+ * Constructor: mxConnectionHandler
+ *
+ * Constructs an event handler that connects vertices using the specified
+ * factory method to create the new edges. Modify
+ * <mxConstants.ACTIVE_REGION> to setup the region on a cell which triggers
+ * the creation of a new connection or use connect icons as explained
+ * above.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * factoryMethod - Optional function to create the edge. The function takes
+ * the source and target <mxCell> as the first and second argument and an
+ * optional cell style from the preview as the third argument. It returns
+ * the <mxCell> that represents the new edge.
+ */
+function mxConnectionHandler(graph, factoryMethod)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.factoryMethod = factoryMethod;
+ this.init();
+ }
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxConnectionHandler.prototype = new mxEventSource();
+mxConnectionHandler.prototype.constructor = mxConnectionHandler;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxConnectionHandler.prototype.graph = null;
+
+/**
+ * Variable: factoryMethod
+ *
+ * Function that is used for creating new edges. The function takes the
+ * source and target <mxCell> as the first and second argument and returns
+ * a new <mxCell> that represents the edge. This is used in <createEdge>.
+ */
+mxConnectionHandler.prototype.factoryMethod = true;
+
+/**
+ * Variable: moveIconFront
+ *
+ * Specifies if icons should be displayed inside the graph container instead
+ * of the overlay pane. This is used for HTML labels on vertices which hide
+ * the connect icon. This has precendence over <moveIconBack> when set
+ * to true. Default is false.
+ */
+mxConnectionHandler.prototype.moveIconFront = false;
+
+/**
+ * Variable: moveIconBack
+ *
+ * Specifies if icons should be moved to the back of the overlay pane. This can
+ * be set to true if the icons of the connection handler conflict with other
+ * handles, such as the vertex label move handle. Default is false.
+ */
+mxConnectionHandler.prototype.moveIconBack = false;
+
+/**
+ * Variable: connectImage
+ *
+ * <mxImage> that is used to trigger the creation of a new connection. This
+ * is used in <createIcons>. Default is null.
+ */
+mxConnectionHandler.prototype.connectImage = null;
+
+/**
+ * Variable: targetConnectImage
+ *
+ * Specifies if the connect icon should be centered on the target state
+ * while connections are being previewed. Default is false.
+ */
+mxConnectionHandler.prototype.targetConnectImage = false;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxConnectionHandler.prototype.enabled = true;
+
+/**
+ * Variable: select
+ *
+ * Specifies if new edges should be selected. Default is true.
+ */
+mxConnectionHandler.prototype.select = true;
+
+/**
+ * Variable: createTarget
+ *
+ * Specifies if <createTargetVertex> should be called if no target was under the
+ * mouse for the new connection. Setting this to true means the connection
+ * will be drawn as valid if no target is under the mouse, and
+ * <createTargetVertex> will be called before the connection is created between
+ * the source cell and the newly created vertex in <createTargetVertex>, which
+ * can be overridden to create a new target. Default is false.
+ */
+mxConnectionHandler.prototype.createTarget = false;
+
+/**
+ * Variable: marker
+ *
+ * Holds the <mxTerminalMarker> used for finding source and target cells.
+ */
+mxConnectionHandler.prototype.marker = null;
+
+/**
+ * Variable: constraintHandler
+ *
+ * Holds the <mxConstraintHandler> used for drawing and highlighting
+ * constraints.
+ */
+mxConnectionHandler.prototype.constraintHandler = null;
+
+/**
+ * Variable: error
+ *
+ * Holds the current validation error while connections are being created.
+ */
+mxConnectionHandler.prototype.error = null;
+
+/**
+ * Variable: waypointsEnabled
+ *
+ * Specifies if single clicks should add waypoints on the new edge. Default is
+ * false.
+ */
+mxConnectionHandler.prototype.waypointsEnabled = false;
+
+/**
+ * Variable: tapAndHoldEnabled
+ *
+ * Specifies if tap and hold should be used for starting connections on touch-based
+ * devices. Default is true.
+ */
+mxConnectionHandler.prototype.tapAndHoldEnabled = true;
+
+/**
+ * Variable: tapAndHoldDelay
+ *
+ * Specifies the time for a tap and hold. Default is 500 ms.
+ */
+mxConnectionHandler.prototype.tapAndHoldDelay = 500;
+
+/**
+ * Variable: tapAndHoldInProgress
+ *
+ * True if the timer for tap and hold events is running.
+ */
+mxConnectionHandler.prototype.tapAndHoldInProgress = false;
+
+/**
+ * Variable: tapAndHoldValid
+ *
+ * True as long as the timer is running and the touch events
+ * stay within the given <tapAndHoldTolerance>.
+ */
+mxConnectionHandler.prototype.tapAndHoldValid = false;
+
+/**
+ * Variable: tapAndHoldTolerance
+ *
+ * Specifies the tolerance for a tap and hold. Default is 4 pixels.
+ */
+mxConnectionHandler.prototype.tapAndHoldTolerance = 4;
+
+/**
+ * Variable: initialTouchX
+ *
+ * Holds the x-coordinate of the intial touch event for tap and hold.
+ */
+mxConnectionHandler.prototype.initialTouchX = 0;
+
+/**
+ * Variable: initialTouchY
+ *
+ * Holds the y-coordinate of the intial touch event for tap and hold.
+ */
+mxConnectionHandler.prototype.initialTouchY = 0;
+
+/**
+ * Variable: ignoreMouseDown
+ *
+ * Specifies if the connection handler should ignore the state of the mouse
+ * button when highlighting the source. Default is false, that is, the
+ * handler only highlights the source if no button is being pressed.
+ */
+mxConnectionHandler.prototype.ignoreMouseDown = false;
+
+/**
+ * Variable: first
+ *
+ * Holds the <mxPoint> where the mouseDown took place while the handler is
+ * active.
+ */
+mxConnectionHandler.prototype.first = null;
+
+/**
+ * Variable: connectIconOffset
+ *
+ * Holds the offset for connect icons during connection preview.
+ * Default is mxPoint(0, <mxConstants.TOOLTIP_VERTICAL_OFFSET>).
+ * Note that placing the icon under the mouse pointer with an
+ * offset of (0,0) will affect hit detection.
+ */
+mxConnectionHandler.prototype.connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET);
+
+/**
+ * Variable: edgeState
+ *
+ * Optional <mxCellState> that represents the preview edge while the
+ * handler is active. This is created in <createEdgeState>.
+ */
+mxConnectionHandler.prototype.edgeState = null;
+
+/**
+ * Variable: changeHandler
+ *
+ * Holds the change event listener for later removal.
+ */
+mxConnectionHandler.prototype.changeHandler = null;
+
+/**
+ * Variable: drillHandler
+ *
+ * Holds the drill event listener for later removal.
+ */
+mxConnectionHandler.prototype.drillHandler = null;
+
+/**
+ * Variable: mouseDownCounter
+ *
+ * Counts the number of mouseDown events since the start. The initial mouse
+ * down event counts as 1.
+ */
+mxConnectionHandler.prototype.mouseDownCounter = 0;
+
+/**
+ * Variable: movePreviewAway
+ *
+ * Switch to enable moving the preview away from the mousepointer. This is required in browsers
+ * where the preview cannot be made transparent to events and if the built-in hit detection on
+ * the HTML elements in the page should be used. Default is the value of <mxClient.IS_VML>.
+ */
+mxConnectionHandler.prototype.movePreviewAway = mxClient.IS_VML;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxConnectionHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxConnectionHandler.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isCreateTarget
+ *
+ * Returns <createTarget>.
+ */
+mxConnectionHandler.prototype.isCreateTarget = function()
+{
+ return this.createTarget;
+};
+
+/**
+ * Function: setCreateTarget
+ *
+ * Sets <createTarget>.
+ */
+mxConnectionHandler.prototype.setCreateTarget = function(value)
+{
+ this.createTarget = value;
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates the preview shape for new connections.
+ */
+mxConnectionHandler.prototype.createShape = function()
+{
+ // Creates the edge preview
+ var shape = new mxPolyline([], mxConstants.INVALID_COLOR);
+ shape.isDashed = true;
+ shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ shape.init(this.graph.getView().getOverlayPane());
+
+ // Event-transparency
+ if (this.graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ // Sets event transparency on the internal shapes that represent
+ // the actual dashed line on the screen
+ shape.pipe.setAttribute('style', 'pointer-events:none;');
+ shape.innerNode.setAttribute('style', 'pointer-events:none;');
+ }
+ else
+ {
+ // Workaround no event transparency for preview in IE
+ // FIXME: 3,3 pixel offset for custom hit detection in IE
+ var getState = mxUtils.bind(this, function(evt)
+ {
+ var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ return this.graph.view.getState(this.graph.getCellAt(pt.x, pt.y));
+ });
+
+ // Redirects events on the shape to the graph
+ mxEvent.redirectMouseEvents(shape.node, this.graph, getState);
+ }
+
+ return shape;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this connection handler. This should
+ * be invoked if <mxGraph.container> is assigned after the connection
+ * handler has been created.
+ */
+mxConnectionHandler.prototype.init = function()
+{
+ this.graph.addMouseListener(this);
+ this.marker = this.createMarker();
+ this.constraintHandler = new mxConstraintHandler(this.graph);
+
+ // Redraws the icons if the graph changes
+ this.changeHandler = mxUtils.bind(this, function(sender)
+ {
+ if (this.iconState != null)
+ {
+ this.iconState = this.graph.getView().getState(this.iconState.cell);
+ }
+
+ if (this.iconState != null)
+ {
+ this.redrawIcons(this.icons, this.iconState);
+ }
+ else
+ {
+ this.destroyIcons(this.icons);
+ this.previous = null;
+ }
+
+ this.constraintHandler.reset();
+ });
+
+ this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+ this.graph.getView().addListener(mxEvent.SCALE, this.changeHandler);
+ this.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler);
+ this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler);
+
+ // Removes the icon if we step into/up or start editing
+ this.drillHandler = mxUtils.bind(this, function(sender)
+ {
+ this.destroyIcons(this.icons);
+ });
+
+ this.graph.addListener(mxEvent.START_EDITING, this.drillHandler);
+ this.graph.getView().addListener(mxEvent.DOWN, this.drillHandler);
+ this.graph.getView().addListener(mxEvent.UP, this.drillHandler);
+};
+
+/**
+ * Function: isConnectableCell
+ *
+ * Returns true if the given cell is connectable. This is a hook to
+ * disable floating connections. This implementation returns true.
+ */
+mxConnectionHandler.prototype.isConnectableCell = function(cell)
+{
+ return true;
+};
+
+/**
+ * Function: createMarker
+ *
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxConnectionHandler.prototype.createMarker = function()
+{
+ var marker = new mxCellMarker(this.graph);
+ marker.hotspotEnabled = true;
+
+ // Overrides to return cell at location only if valid (so that
+ // there is no highlight for invalid cells)
+ marker.getCell = mxUtils.bind(this, function(evt, cell)
+ {
+ var cell = mxCellMarker.prototype.getCell.apply(marker, arguments);
+ this.error = null;
+
+ if (!this.isConnectableCell(cell))
+ {
+ return null;
+ }
+
+ if (cell != null)
+ {
+ if (this.isConnecting())
+ {
+ if (this.previous != null)
+ {
+ this.error = this.validateConnection(this.previous.cell, cell);
+
+ if (this.error != null && this.error.length == 0)
+ {
+ cell = null;
+
+ // Enables create target inside groups
+ if (this.isCreateTarget())
+ {
+ this.error = null;
+ }
+ }
+ }
+ }
+ else if (!this.isValidSource(cell))
+ {
+ cell = null;
+ }
+ }
+ else if (this.isConnecting() && !this.isCreateTarget() &&
+ !this.graph.allowDanglingEdges)
+ {
+ this.error = '';
+ }
+
+ return cell;
+ });
+
+ // Sets the highlight color according to validateConnection
+ marker.isValidState = mxUtils.bind(this, function(state)
+ {
+ if (this.isConnecting())
+ {
+ return this.error == null;
+ }
+ else
+ {
+ return mxCellMarker.prototype.isValidState.apply(marker, arguments);
+ }
+ });
+
+ // Overrides to use marker color only in highlight mode or for
+ // target selection
+ marker.getMarkerColor = mxUtils.bind(this, function(evt, state, isValid)
+ {
+ return (this.connectImage == null || this.isConnecting()) ?
+ mxCellMarker.prototype.getMarkerColor.apply(marker, arguments) :
+ null;
+ });
+
+ // Overrides to use hotspot only for source selection otherwise
+ // intersects always returns true when over a cell
+ marker.intersects = mxUtils.bind(this, function(state, evt)
+ {
+ if (this.connectImage != null || this.isConnecting())
+ {
+ return true;
+ }
+
+ return mxCellMarker.prototype.intersects.apply(marker, arguments);
+ });
+
+ return marker;
+};
+
+/**
+ * Function: start
+ *
+ * Starts a new connection for the given state and coordinates.
+ */
+mxConnectionHandler.prototype.start = function(state, x, y, edgeState)
+{
+ this.previous = state;
+ this.first = new mxPoint(x, y);
+ this.edgeState = (edgeState != null) ? edgeState : this.createEdgeState(null);
+
+ // Marks the source state
+ this.marker.currentColor = this.marker.validColor;
+ this.marker.markedState = state;
+ this.marker.mark();
+
+ this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+};
+
+/**
+ * Function: isConnecting
+ *
+ * Returns true if the source terminal has been clicked and a new
+ * connection is currently being previewed.
+ */
+mxConnectionHandler.prototype.isConnecting = function()
+{
+ return this.first != null && this.shape != null;
+};
+
+/**
+ * Function: isValidSource
+ *
+ * Returns <mxGraph.isValidSource> for the given source terminal.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the source terminal.
+ */
+mxConnectionHandler.prototype.isValidSource = function(cell)
+{
+ return this.graph.isValidSource(cell);
+};
+
+/**
+ * Function: isValidTarget
+ *
+ * Returns true. The call to <mxGraph.isValidTarget> is implicit by calling
+ * <mxGraph.getEdgeValidationError> in <validateConnection>. This is an
+ * additional hook for disabling certain targets in this specific handler.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the target terminal.
+ */
+mxConnectionHandler.prototype.isValidTarget = function(cell)
+{
+ return true;
+};
+
+/**
+ * Function: validateConnection
+ *
+ * Returns the error message or an empty string if the connection for the
+ * given source target pair is not valid. Otherwise it returns null. This
+ * implementation uses <mxGraph.getEdgeValidationError>.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxConnectionHandler.prototype.validateConnection = function(source, target)
+{
+ if (!this.isValidTarget(target))
+ {
+ return '';
+ }
+
+ return this.graph.getEdgeValidationError(null, source, target);
+};
+
+/**
+ * Function: getConnectImage
+ *
+ * Hook to return the <mxImage> used for the connection icon of the given
+ * <mxCellState>. This implementation returns <connectImage>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose connect image should be returned.
+ */
+mxConnectionHandler.prototype.getConnectImage = function(state)
+{
+ return this.connectImage;
+};
+
+/**
+ * Function: isMoveIconToFrontForState
+ *
+ * Returns true if the state has a HTML label in the graph's container, otherwise
+ * it returns <moveIconFront>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose connect icons should be returned.
+ */
+mxConnectionHandler.prototype.isMoveIconToFrontForState = function(state)
+{
+ if (state.text != null && state.text.node.parentNode == this.graph.container)
+ {
+ return true;
+ }
+
+ return this.moveIconFront;
+};
+
+/**
+ * Function: createIcons
+ *
+ * Creates the array <mxImageShapes> that represent the connect icons for
+ * the given <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose connect icons should be returned.
+ */
+mxConnectionHandler.prototype.createIcons = function(state)
+{
+ var image = this.getConnectImage(state);
+
+ if (image != null && state != null)
+ {
+ this.iconState = state;
+ var icons = [];
+
+ // Cannot use HTML for the connect icons because the icon receives all
+ // mouse move events in IE, must use VML and SVG instead even if the
+ // connect-icon appears behind the selection border and the selection
+ // border consumes the events before the icon gets a chance
+ var bounds = new mxRectangle(0, 0, image.width, image.height);
+ var icon = new mxImageShape(bounds, image.src, null, null, 0);
+ icon.preserveImageAspect = false;
+
+ if (this.isMoveIconToFrontForState(state))
+ {
+ icon.dialect = mxConstants.DIALECT_STRICTHTML;
+ icon.init(this.graph.container);
+ }
+ else
+ {
+ icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_SVG :
+ mxConstants.DIALECT_VML;
+ icon.init(this.graph.getView().getOverlayPane());
+
+ // Move the icon back in the overlay pane
+ if (this.moveIconBack && icon.node.previousSibling != null)
+ {
+ icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
+ }
+ }
+
+ icon.node.style.cursor = mxConstants.CURSOR_CONNECT;
+
+ // Events transparency
+ var getState = mxUtils.bind(this, function()
+ {
+ return (this.currentState != null) ? this.currentState : state;
+ });
+
+ // Updates the local icon before firing the mouse down event.
+ var mouseDown = mxUtils.bind(this, function(evt)
+ {
+ if (!mxEvent.isConsumed(evt))
+ {
+ this.icon = icon;
+ this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, getState()));
+ }
+ });
+
+ mxEvent.redirectMouseEvents(icon.node, this.graph, getState, mouseDown);
+
+ icons.push(icon);
+ this.redrawIcons(icons, this.iconState);
+
+ return icons;
+ }
+
+ return null;
+};
+
+/**
+ * Function: redrawIcons
+ *
+ * Redraws the given array of <mxImageShapes>.
+ *
+ * Parameters:
+ *
+ * icons - Optional array of <mxImageShapes> to be redrawn.
+ */
+mxConnectionHandler.prototype.redrawIcons = function(icons, state)
+{
+ if (icons != null && icons[0] != null && state != null)
+ {
+ var pos = this.getIconPosition(icons[0], state);
+ icons[0].bounds.x = pos.x;
+ icons[0].bounds.y = pos.y;
+ icons[0].redraw();
+ }
+};
+
+/**
+ * Function: redrawIcons
+ *
+ * Redraws the given array of <mxImageShapes>.
+ *
+ * Parameters:
+ *
+ * icons - Optional array of <mxImageShapes> to be redrawn.
+ */
+mxConnectionHandler.prototype.getIconPosition = function(icon, state)
+{
+ var scale = this.graph.getView().scale;
+ var cx = state.getCenterX();
+ var cy = state.getCenterY();
+
+ if (this.graph.isSwimlane(state.cell))
+ {
+ var size = this.graph.getStartSize(state.cell);
+
+ cx = (size.width != 0) ? state.x + size.width * scale / 2 : cx;
+ cy = (size.height != 0) ? state.y + size.height * scale / 2 : cy;
+ }
+
+ return new mxPoint(cx - icon.bounds.width / 2,
+ cy - icon.bounds.height / 2);
+};
+
+/**
+ * Function: destroyIcons
+ *
+ * Destroys the given array of <mxImageShapes>.
+ *
+ * Parameters:
+ *
+ * icons - Optional array of <mxImageShapes> to be destroyed.
+ */
+mxConnectionHandler.prototype.destroyIcons = function(icons)
+{
+ if (icons != null)
+ {
+ this.iconState = null;
+
+ for (var i = 0; i < icons.length; i++)
+ {
+ icons[i].destroy();
+ }
+ }
+};
+
+/**
+ * Function: isStartEvent
+ *
+ * Returns true if the given mouse down event should start this handler. The
+ * This implementation returns true if the event does not force marquee
+ * selection, and the currentConstraint and currentFocus of the
+ * <constraintHandler> are not null, or <previous> and <error> are not null and
+ * <icons> is null or <icons> and <icon> are not null.
+ */
+mxConnectionHandler.prototype.isStartEvent = function(me)
+{
+ return !this.graph.isForceMarqueeEvent(me.getEvent()) &&
+ ((this.constraintHandler.currentFocus != null &&
+ this.constraintHandler.currentConstraint != null) ||
+ (this.previous != null && this.error == null &&
+ (this.icons == null || (this.icons != null && this.icon != null))));
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by initiating a new connection.
+ */
+mxConnectionHandler.prototype.mouseDown = function(sender, me)
+{
+ this.mouseDownCounter++;
+
+ if (this.isEnabled() && this.graph.isEnabled() && !me.isConsumed() &&
+ !this.isConnecting() && this.isStartEvent(me))
+ {
+ if (this.constraintHandler.currentConstraint != null &&
+ this.constraintHandler.currentFocus != null &&
+ this.constraintHandler.currentPoint != null)
+ {
+ this.sourceConstraint = this.constraintHandler.currentConstraint;
+ this.previous = this.constraintHandler.currentFocus;
+ this.first = this.constraintHandler.currentPoint.clone();
+ }
+ else
+ {
+ // Stores the location of the initial mousedown
+ this.first = new mxPoint(me.getGraphX(), me.getGraphY());
+ }
+
+ this.edgeState = this.createEdgeState(me);
+ this.mouseDownCounter = 1;
+
+ if (this.waypointsEnabled && this.shape == null)
+ {
+ this.waypoints = null;
+ this.shape = this.createShape();
+ }
+
+ // Stores the starting point in the geometry of the preview
+ if (this.previous == null && this.edgeState != null)
+ {
+ var pt = this.graph.getPointForEvent(me.getEvent());
+ this.edgeState.cell.geometry.setTerminalPoint(pt, true);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+
+ me.consume();
+ }
+ // Handles connecting via tap and hold
+ else if (mxClient.IS_TOUCH && this.tapAndHoldEnabled && !this.tapAndHoldInProgress &&
+ this.isEnabled() && this.graph.isEnabled() && !this.isConnecting())
+ {
+ this.tapAndHoldInProgress = true;
+ this.initialTouchX = me.getX();
+ this.initialTouchY = me.getY();
+ var state = this.graph.view.getState(this.marker.getCell(me));
+
+ var handler = function()
+ {
+ if (this.tapAndHoldValid)
+ {
+ this.tapAndHold(me, state);
+ }
+
+ this.tapAndHoldInProgress = false;
+ this.tapAndHoldValid = false;
+ };
+
+ if (this.tapAndHoldThread)
+ {
+ window.clearTimeout(this.tapAndHoldThread);
+ }
+
+ this.tapAndHoldThread = window.setTimeout(mxUtils.bind(this, handler), this.tapAndHoldDelay);
+ this.tapAndHoldValid = true;
+ }
+
+ this.selectedIcon = this.icon;
+ this.icon = null;
+};
+
+/**
+ * Function: tapAndHold
+ *
+ * Handles the <mxMouseEvent> by highlighting the <mxCellState>.
+ *
+ * Parameters:
+ *
+ * me - <mxMouseEvent> that represents the touch event.
+ * state - Optional <mxCellState> that is associated with the event.
+ */
+mxConnectionHandler.prototype.tapAndHold = function(me, state)
+{
+ if (state != null)
+ {
+ this.marker.currentColor = this.marker.validColor;
+ this.marker.markedState = state;
+ this.marker.mark();
+
+ this.first = new mxPoint(me.getGraphX(), me.getGraphY());
+ this.edgeState = this.createEdgeState(me);
+ this.previous = state;
+ this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+ }
+};
+
+/**
+ * Function: isImmediateConnectSource
+ *
+ * Returns true if a tap on the given source state should immediately start
+ * connecting. This implementation returns true if the state is not movable
+ * in the graph.
+ */
+mxConnectionHandler.prototype.isImmediateConnectSource = function(state)
+{
+ return !this.graph.isCellMovable(state.cell);
+};
+
+/**
+ * Function: createEdgeState
+ *
+ * Hook to return an <mxCellState> which may be used during the preview.
+ * This implementation returns null.
+ *
+ * Use the following code to create a preview for an existing edge style:
+ *
+ * [code]
+ * graph.connectionHandler.createEdgeState = function(me)
+ * {
+ * var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle');
+ *
+ * return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));
+ * };
+ * [/code]
+ */
+mxConnectionHandler.prototype.createEdgeState = function(me)
+{
+ return null;
+};
+
+/**
+ * Function: updateCurrentState
+ *
+ * Updates the current state for a given mouse move event by using
+ * the <marker>.
+ */
+mxConnectionHandler.prototype.updateCurrentState = function(me)
+{
+ var state = this.marker.process(me);
+ this.constraintHandler.update(me, this.first == null);
+ this.currentState = state;
+};
+
+/**
+ * Function: convertWaypoint
+ *
+ * Converts the given point from screen coordinates to model coordinates.
+ */
+mxConnectionHandler.prototype.convertWaypoint = function(point)
+{
+ var scale = this.graph.getView().getScale();
+ var tr = this.graph.getView().getTranslate();
+
+ point.x = point.x / scale - tr.x;
+ point.y = point.y / scale - tr.y;
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating the preview edge or by highlighting
+ * a possible source or target terminal.
+ */
+mxConnectionHandler.prototype.mouseMove = function(sender, me)
+{
+ if (this.tapAndHoldValid)
+ {
+ this.tapAndHoldValid =
+ Math.abs(this.initialTouchX - me.getX()) < this.tapAndHoldTolerance &&
+ Math.abs(this.initialTouchY - me.getY()) < this.tapAndHoldTolerance;
+ }
+
+ if (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown))
+ {
+ // Handles special case when handler is disabled during highlight
+ if (!this.isEnabled() && this.currentState != null)
+ {
+ this.destroyIcons(this.icons);
+ this.currentState = null;
+ }
+
+ if (this.first != null || (this.isEnabled() && this.graph.isEnabled()))
+ {
+ this.updateCurrentState(me);
+ }
+
+ if (this.first != null)
+ {
+ var view = this.graph.getView();
+ var scale = view.scale;
+ var point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale,
+ this.graph.snap(me.getGraphY() / scale) * scale);
+ var constraint = null;
+ var current = point;
+
+ // Uses the current point from the constraint handler if available
+ if (this.constraintHandler.currentConstraint != null &&
+ this.constraintHandler.currentFocus != null &&
+ this.constraintHandler.currentPoint != null)
+ {
+ constraint = this.constraintHandler.currentConstraint;
+ current = this.constraintHandler.currentPoint.clone();
+ }
+
+ var pt2 = this.first;
+
+ // Moves the connect icon with the mouse
+ if (this.selectedIcon != null)
+ {
+ var w = this.selectedIcon.bounds.width;
+ var h = this.selectedIcon.bounds.height;
+
+ if (this.currentState != null && this.targetConnectImage)
+ {
+ var pos = this.getIconPosition(this.selectedIcon, this.currentState);
+ this.selectedIcon.bounds.x = pos.x;
+ this.selectedIcon.bounds.y = pos.y;
+ }
+ else
+ {
+ var bounds = new mxRectangle(me.getGraphX() + this.connectIconOffset.x,
+ me.getGraphY() + this.connectIconOffset.y, w, h);
+ this.selectedIcon.bounds = bounds;
+ }
+
+ this.selectedIcon.redraw();
+ }
+
+ // Uses edge state to compute the terminal points
+ if (this.edgeState != null)
+ {
+ this.edgeState.absolutePoints = [null, (this.currentState != null) ? null : current];
+ this.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, true, this.sourceConstraint);
+
+ if (this.currentState != null)
+ {
+ if (constraint == null)
+ {
+ constraint = this.graph.getConnectionConstraint(this.edgeState, this.previous, false);
+ }
+
+ this.edgeState.setAbsoluteTerminalPoint(null, false);
+ this.graph.view.updateFixedTerminalPoint(this.edgeState, this.currentState, false, constraint);
+ }
+
+ // Scales and translates the waypoints to the model
+ var realPoints = null;
+
+ if (this.waypoints != null)
+ {
+ realPoints = [];
+
+ for (var i = 0; i < this.waypoints.length; i++)
+ {
+ var pt = this.waypoints[i].clone();
+ this.convertWaypoint(pt);
+ realPoints[i] = pt;
+ }
+ }
+
+ this.graph.view.updatePoints(this.edgeState, realPoints, this.previous, this.currentState);
+ this.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState);
+ current = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1];
+ pt2 = this.edgeState.absolutePoints[0];
+ }
+ else
+ {
+ if (this.currentState != null)
+ {
+ if (this.constraintHandler.currentConstraint == null)
+ {
+ var tmp = this.getTargetPerimeterPoint(this.currentState, me);
+
+ if (tmp != null)
+ {
+ current = tmp;
+ }
+ }
+ }
+
+ // Computes the source perimeter point
+ if (this.sourceConstraint == null && this.previous != null)
+ {
+ var next = (this.waypoints != null && this.waypoints.length > 0) ?
+ this.waypoints[0] : current;
+ var tmp = this.getSourcePerimeterPoint(this.previous, next, me);
+
+ if (tmp != null)
+ {
+ pt2 = tmp;
+ }
+ }
+ }
+
+ // Makes sure the cell under the mousepointer can be detected
+ // by moving the preview shape away from the mouse. This
+ // makes sure the preview shape does not prevent the detection
+ // of the cell under the mousepointer even for slow gestures.
+ if (this.currentState == null && this.movePreviewAway)
+ {
+ var tmp = pt2;
+
+ if (this.edgeState != null && this.edgeState.absolutePoints.length > 2)
+ {
+ var tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2];
+
+ if (tmp2 != null)
+ {
+ tmp = tmp2;
+ }
+ }
+
+ var dx = current.x - tmp.x;
+ var dy = current.y - tmp.y;
+
+ var len = Math.sqrt(dx * dx + dy * dy);
+
+ if (len == 0)
+ {
+ return;
+ }
+
+ current.x -= dx * 4 / len;
+ current.y -= dy * 4 / len;
+ }
+
+ // Creates the preview shape (lazy)
+ if (this.shape == null)
+ {
+ var dx = Math.abs(point.x - this.first.x);
+ var dy = Math.abs(point.y - this.first.y);
+
+ if (dx > this.graph.tolerance || dy > this.graph.tolerance)
+ {
+ this.shape = this.createShape();
+
+ // Revalidates current connection
+ this.updateCurrentState(me);
+ }
+ }
+
+ // Updates the points in the preview edge
+ if (this.shape != null)
+ {
+ if (this.edgeState != null)
+ {
+ this.shape.points = this.edgeState.absolutePoints;
+ }
+ else
+ {
+ var pts = [pt2];
+
+ if (this.waypoints != null)
+ {
+ pts = pts.concat(this.waypoints);
+ }
+
+ pts.push(current);
+ this.shape.points = pts;
+ }
+
+ this.drawPreview();
+ }
+
+ mxEvent.consume(me.getEvent());
+ me.consume();
+ }
+ else if(!this.isEnabled() || !this.graph.isEnabled())
+ {
+ this.constraintHandler.reset();
+ }
+ else if (this.previous != this.currentState && this.edgeState == null)
+ {
+ this.destroyIcons(this.icons);
+ this.icons = null;
+
+ // Sets the cursor on the current shape
+ if (this.currentState != null && this.error == null)
+ {
+ this.icons = this.createIcons(this.currentState);
+
+ if (this.icons == null)
+ {
+ this.currentState.setCursor(mxConstants.CURSOR_CONNECT);
+ me.consume();
+ }
+ }
+
+ this.previous = this.currentState;
+ }
+ else if (this.previous == this.currentState && this.currentState != null && this.icons == null &&
+ !this.graph.isMouseDown)
+ {
+ // Makes sure that no cursors are changed
+ me.consume();
+ }
+
+ if (this.constraintHandler.currentConstraint != null)
+ {
+ this.marker.reset();
+ }
+
+ if (!this.graph.isMouseDown && this.currentState != null && this.icons != null)
+ {
+ var hitsIcon = false;
+ var target = me.getSource();
+
+ for (var i = 0; i < this.icons.length && !hitsIcon; i++)
+ {
+ hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node;
+ }
+
+ if (!hitsIcon)
+ {
+ this.updateIcons(this.currentState, this.icons, me);
+ }
+ }
+ }
+ else
+ {
+ this.constraintHandler.reset();
+ }
+};
+
+/**
+ * Function: getTargetPerimeterPoint
+ *
+ * Returns the perimeter point for the given target state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the target cell state.
+ * me - <mxMouseEvent> that represents the mouse move.
+ */
+mxConnectionHandler.prototype.getTargetPerimeterPoint = function(state, me)
+{
+ var result = null;
+ var view = state.view;
+ var targetPerimeter = view.getPerimeterFunction(state);
+
+ if (targetPerimeter != null)
+ {
+ var next = (this.waypoints != null && this.waypoints.length > 0) ?
+ this.waypoints[this.waypoints.length - 1] :
+ new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
+ var tmp = targetPerimeter(view.getPerimeterBounds(state),
+ this.edgeState, next, false);
+
+ if (tmp != null)
+ {
+ result = tmp;
+ }
+ }
+ else
+ {
+ result = new mxPoint(state.getCenterX(), state.getCenterY());
+ }
+
+ return result;
+};
+
+/**
+ * Function: getSourcePerimeterPoint
+ *
+ * Hook to update the icon position(s) based on a mouseOver event. This is
+ * an empty implementation.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the target cell state.
+ * next - <mxPoint> that represents the next point along the previewed edge.
+ * me - <mxMouseEvent> that represents the mouse move.
+ */
+mxConnectionHandler.prototype.getSourcePerimeterPoint = function(state, next, me)
+{
+ var result = null;
+ var view = state.view;
+ var sourcePerimeter = view.getPerimeterFunction(state);
+
+ if (sourcePerimeter != null)
+ {
+ var tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false);
+
+ if (tmp != null)
+ {
+ result = tmp;
+ }
+ }
+ else
+ {
+ result = new mxPoint(state.getCenterX(), state.getCenterY());
+ }
+
+ return result;
+};
+
+
+/**
+ * Function: updateIcons
+ *
+ * Hook to update the icon position(s) based on a mouseOver event. This is
+ * an empty implementation.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> under the mouse.
+ * icons - Array of currently displayed icons.
+ * me - <mxMouseEvent> that contains the mouse event.
+ */
+mxConnectionHandler.prototype.updateIcons = function(state, icons, me)
+{
+ // empty
+};
+
+/**
+ * Function: isStopEvent
+ *
+ * Returns true if the given mouse up event should stop this handler. The
+ * connection will be created if <error> is null. Note that this is only
+ * called if <waypointsEnabled> is true. This implemtation returns true
+ * if there is a cell state in the given event.
+ */
+mxConnectionHandler.prototype.isStopEvent = function(me)
+{
+ return me.getState() != null;
+};
+
+/**
+ * Function: addWaypoint
+ *
+ * Adds the waypoint for the given event to <waypoints>.
+ */
+mxConnectionHandler.prototype.addWaypointForEvent = function(me)
+{
+ var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
+ var dx = Math.abs(point.x - this.first.x);
+ var dy = Math.abs(point.y - this.first.y);
+ var addPoint = this.waypoints != null || (this.mouseDownCounter > 1 &&
+ (dx > this.graph.tolerance || dy > this.graph.tolerance));
+
+ if (addPoint)
+ {
+ if (this.waypoints == null)
+ {
+ this.waypoints = [];
+ }
+
+ var scale = this.graph.view.scale;
+ var point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale,
+ this.graph.snap(me.getGraphY() / scale) * scale);
+ this.waypoints.push(point);
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by inserting the new connection.
+ */
+mxConnectionHandler.prototype.mouseUp = function(sender, me)
+{
+ if (!me.isConsumed() && this.isConnecting())
+ {
+ if (this.waypointsEnabled && !this.isStopEvent(me))
+ {
+ this.addWaypointForEvent(me);
+ me.consume();
+
+ return;
+ }
+
+ // Inserts the edge if no validation error exists
+ if (this.error == null)
+ {
+ var source = (this.previous != null) ? this.previous.cell : null;
+ var target = null;
+
+ if (this.constraintHandler.currentConstraint != null &&
+ this.constraintHandler.currentFocus != null)
+ {
+ target = this.constraintHandler.currentFocus.cell;
+ }
+
+ if (target == null && this.marker.hasValidState())
+ {
+ target = this.marker.validState.cell;
+ }
+
+ this.connect(source, target, me.getEvent(), me.getCell());
+ }
+ else
+ {
+ // Selects the source terminal for self-references
+ if (this.previous != null && this.marker.validState != null &&
+ this.previous.cell == this.marker.validState.cell)
+ {
+ this.graph.selectCellForEvent(this.marker.source, evt);
+ }
+
+ // Displays the error message if it is not an empty string,
+ // for empty error messages, the event is silently dropped
+ if (this.error.length > 0)
+ {
+ this.graph.validationAlert(this.error);
+ }
+ }
+
+ // Redraws the connect icons and resets the handler state
+ this.destroyIcons(this.icons);
+ me.consume();
+ }
+
+ if (this.first != null)
+ {
+ this.reset();
+ }
+
+ this.tapAndHoldInProgress = false;
+ this.tapAndHoldValid = false;
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this handler.
+ */
+mxConnectionHandler.prototype.reset = function()
+{
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+
+ this.destroyIcons(this.icons);
+ this.icons = null;
+ this.marker.reset();
+ this.constraintHandler.reset();
+ this.selectedIcon = null;
+ this.edgeState = null;
+ this.previous = null;
+ this.error = null;
+ this.sourceConstraint = null;
+ this.mouseDownCounter = 0;
+ this.first = null;
+ this.icon = null;
+
+ this.fireEvent(new mxEventObject(mxEvent.RESET));
+};
+
+/**
+ * Function: drawPreview
+ *
+ * Redraws the preview edge using the color and width returned by
+ * <getEdgeColor> and <getEdgeWidth>.
+ */
+mxConnectionHandler.prototype.drawPreview = function()
+{
+ var valid = this.error == null;
+ var color = this.getEdgeColor(valid);
+
+ if (this.shape.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.shape.innerNode.setAttribute('stroke', color);
+ }
+ else
+ {
+ this.shape.node.strokecolor = color;
+ }
+
+ this.shape.strokewidth = this.getEdgeWidth(valid);
+ this.shape.redraw();
+
+ // Workaround to force a repaint in AppleWebKit
+ mxUtils.repaintGraph(this.graph, this.shape.points[1]);
+};
+
+/**
+ * Function: getEdgeColor
+ *
+ * Returns the color used to draw the preview edge. This returns green if
+ * there is no edge validation error and red otherwise.
+ *
+ * Parameters:
+ *
+ * valid - Boolean indicating if the color for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.getEdgeColor = function(valid)
+{
+ return (valid) ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR;
+};
+
+/**
+ * Function: getEdgeWidth
+ *
+ * Returns the width used to draw the preview edge. This returns 3 if
+ * there is no edge validation error and 1 otherwise.
+ *
+ * Parameters:
+ *
+ * valid - Boolean indicating if the width for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.getEdgeWidth = function(valid)
+{
+ return (valid) ? 3 : 1;
+};
+
+/**
+ * Function: connect
+ *
+ * Connects the given source and target using a new edge. This
+ * implementation uses <createEdge> to create the edge.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * evt - Mousedown event of the connect gesture.
+ * dropTarget - <mxCell> that represents the cell under the mouse when it was
+ * released.
+ */
+mxConnectionHandler.prototype.connect = function(source, target, evt, dropTarget)
+{
+ if (target != null || this.isCreateTarget() || this.graph.allowDanglingEdges)
+ {
+ // Uses the common parent of source and target or
+ // the default parent to insert the edge
+ var model = this.graph.getModel();
+ var edge = null;
+
+ model.beginUpdate();
+ try
+ {
+ if (source != null && target == null && this.isCreateTarget())
+ {
+ target = this.createTargetVertex(evt, source);
+
+ if (target != null)
+ {
+ dropTarget = this.graph.getDropTarget([target], evt, dropTarget);
+
+ // Disables edges as drop targets if the target cell was created
+ // FIXME: Should not shift if vertex was aligned (same in Java)
+ if (dropTarget == null || !this.graph.getModel().isEdge(dropTarget))
+ {
+ var pstate = this.graph.getView().getState(dropTarget);
+
+ if (pstate != null)
+ {
+ var tmp = model.getGeometry(target);
+ tmp.x -= pstate.origin.x;
+ tmp.y -= pstate.origin.y;
+ }
+ }
+ else
+ {
+ dropTarget = this.graph.getDefaultParent();
+ }
+
+ this.graph.addCell(target, dropTarget);
+ }
+ }
+
+ var parent = this.graph.getDefaultParent();
+
+ if (source != null && target != null &&
+ model.getParent(source) == model.getParent(target) &&
+ model.getParent(model.getParent(source)) != model.getRoot())
+ {
+ parent = model.getParent(source);
+
+ if ((source.geometry != null && source.geometry.relative) &&
+ (target.geometry != null && target.geometry.relative))
+ {
+ parent = model.getParent(parent);
+ }
+ }
+
+ // Uses the value of the preview edge state for inserting
+ // the new edge into the graph
+ var value = null;
+ var style = null;
+
+ if (this.edgeState != null)
+ {
+ value = this.edgeState.cell.value;
+ style = this.edgeState.cell.style;
+ }
+
+ edge = this.insertEdge(parent, null, value, source, target, style);
+
+ if (edge != null)
+ {
+ // Updates the connection constraints
+ this.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint);
+ this.graph.setConnectionConstraint(edge, target, false, this.constraintHandler.currentConstraint);
+
+ // Uses geometry of the preview edge state
+ if (this.edgeState != null)
+ {
+ model.setGeometry(edge, this.edgeState.cell.geometry);
+ }
+
+ // Makes sure the edge has a non-null, relative geometry
+ var geo = model.getGeometry(edge);
+
+ if (geo == null)
+ {
+ geo = new mxGeometry();
+ geo.relative = true;
+
+ model.setGeometry(edge, geo);
+ }
+
+ // Uses scaled waypoints in geometry
+ if (this.waypoints != null && this.waypoints.length > 0)
+ {
+ var s = this.graph.view.scale;
+ var tr = this.graph.view.translate;
+ geo.points = [];
+
+ for (var i = 0; i < this.waypoints.length; i++)
+ {
+ var pt = this.waypoints[i];
+ geo.points.push(new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y));
+ }
+ }
+
+ if (target == null)
+ {
+ var pt = this.graph.getPointForEvent(evt, false);
+ pt.x -= this.graph.panDx / this.graph.view.scale;
+ pt.y -= this.graph.panDy / this.graph.view.scale;
+ geo.setTerminalPoint(pt, false);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CONNECT,
+ 'cell', edge, 'event', evt, 'target', dropTarget));
+ }
+ }
+ catch (e)
+ {
+ mxLog.show();
+ mxLog.debug(e.message);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+
+ if (this.select)
+ {
+ this.selectCells(edge, target);
+ }
+ }
+};
+
+/**
+ * Function: selectCells
+ *
+ * Selects the given edge after adding a new connection. The target argument
+ * contains the target vertex if one has been inserted.
+ */
+mxConnectionHandler.prototype.selectCells = function(edge, target)
+{
+ this.graph.setSelectionCell(edge);
+};
+
+/**
+ * Function: insertEdge
+ *
+ * Creates, inserts and returns the new edge for the given parameters. This
+ * implementation does only use <createEdge> if <factoryMethod> is defined,
+ * otherwise <mxGraph.insertEdge> will be used.
+ */
+mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+ if (this.factoryMethod == null)
+ {
+ return this.graph.insertEdge(parent, id, value, source, target, style);
+ }
+ else
+ {
+ var edge = this.createEdge(value, source, target, style);
+ edge = this.graph.addEdge(edge, parent, source, target);
+
+ return edge;
+ }
+};
+
+/**
+ * Function: createTargetVertex
+ *
+ * Hook method for creating new vertices on the fly if no target was
+ * under the mouse. This is only called if <createTarget> is true and
+ * returns null.
+ *
+ * Parameters:
+ *
+ * evt - Mousedown event of the connect gesture.
+ * source - <mxCell> that represents the source terminal.
+ */
+mxConnectionHandler.prototype.createTargetVertex = function(evt, source)
+{
+ // Uses the first non-relative source
+ var geo = this.graph.getCellGeometry(source);
+
+ while (geo != null && geo.relative)
+ {
+ source = this.graph.getModel().getParent(source);
+ geo = this.graph.getCellGeometry(source);
+ }
+
+ var clone = this.graph.cloneCells([source])[0];
+ var geo = this.graph.getModel().getGeometry(clone);
+
+ if (geo != null)
+ {
+ var point = this.graph.getPointForEvent(evt);
+ geo.x = this.graph.snap(point.x - geo.width / 2) - this.graph.panDx / this.graph.view.scale;
+ geo.y = this.graph.snap(point.y - geo.height / 2) - this.graph.panDy / this.graph.view.scale;
+
+ // Aligns with source if within certain tolerance
+ if (this.first != null)
+ {
+ var sourceState = this.graph.view.getState(source);
+
+ if (sourceState != null)
+ {
+ var tol = this.getAlignmentTolerance();
+
+ if (Math.abs(this.graph.snap(this.first.x) -
+ this.graph.snap(point.x)) <= tol)
+ {
+ geo.x = sourceState.x;
+ }
+ else if (Math.abs(this.graph.snap(this.first.y) -
+ this.graph.snap(point.y)) <= tol)
+ {
+ geo.y = sourceState.y;
+ }
+ }
+ }
+ }
+
+ return clone;
+};
+
+/**
+ * Function: getAlignmentTolerance
+ *
+ * Returns the tolerance for aligning new targets to sources.
+ */
+mxConnectionHandler.prototype.getAlignmentTolerance = function()
+{
+ return (this.graph.isGridEnabled()) ?
+ this.graph.gridSize : this.graph.tolerance;
+};
+
+/**
+ * Function: createEdge
+ *
+ * Creates and returns a new edge using <factoryMethod> if one exists. If
+ * no factory method is defined, then a new default edge is returned. The
+ * source and target arguments are informal, the actual connection is
+ * setup later by the caller of this function.
+ *
+ * Parameters:
+ *
+ * value - Value to be used for creating the edge.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * style - Optional style from the preview edge.
+ */
+mxConnectionHandler.prototype.createEdge = function(value, source, target, style)
+{
+ var edge = null;
+
+ // Creates a new edge using the factoryMethod
+ if (this.factoryMethod != null)
+ {
+ edge = this.factoryMethod(source, target, style);
+ }
+
+ if (edge == null)
+ {
+ edge = new mxCell(value || '');
+ edge.setEdge(true);
+ edge.setStyle(style);
+
+ var geo = new mxGeometry();
+ geo.relative = true;
+ edge.setGeometry(geo);
+ }
+
+ return edge;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes. This should be
+ * called on all instances. It is called automatically for the built-in
+ * instance created for each <mxGraph>.
+ */
+mxConnectionHandler.prototype.destroy = function()
+{
+ this.graph.removeMouseListener(this);
+
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+
+ if (this.marker != null)
+ {
+ this.marker.destroy();
+ this.marker = null;
+ }
+
+ if (this.constraintHandler != null)
+ {
+ this.constraintHandler.destroy();
+ this.constraintHandler = null;
+ }
+
+ if (this.changeHandler != null)
+ {
+ this.graph.getModel().removeListener(this.changeHandler);
+ this.graph.getView().removeListener(this.changeHandler);
+ this.changeHandler = null;
+ }
+
+ if (this.drillHandler != null)
+ {
+ this.graph.removeListener(this.drillHandler);
+ this.graph.getView().removeListener(this.drillHandler);
+ this.drillHandler = null;
+ }
+};
diff --git a/src/js/handler/mxConstraintHandler.js b/src/js/handler/mxConstraintHandler.js
new file mode 100644
index 0000000..39b3ab6
--- /dev/null
+++ b/src/js/handler/mxConstraintHandler.js
@@ -0,0 +1,308 @@
+/**
+ * $Id: mxConstraintHandler.js,v 1.15 2012-11-01 16:13:41 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxConstraintHandler
+ *
+ * Handles constraints on connection targets. This class is in charge of
+ * showing fixed points when the mouse is over a vertex and handles constraints
+ * to establish new connections.
+ *
+ * Constructor: mxConstraintHandler
+ *
+ * Constructs an new constraint handler.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * factoryMethod - Optional function to create the edge. The function takes
+ * the source and target <mxCell> as the first and second argument and
+ * returns the <mxCell> that represents the new edge.
+ */
+function mxConstraintHandler(graph)
+{
+ this.graph = graph;
+};
+
+/**
+ * Variable: pointImage
+ *
+ * <mxImage> to be used as the image for fixed connection points.
+ */
+mxConstraintHandler.prototype.pointImage = new mxImage(mxClient.imageBasePath + '/point.gif', 5, 5);
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxConstraintHandler.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxConstraintHandler.prototype.enabled = true;
+
+/**
+ * Variable: highlightColor
+ *
+ * Specifies the color for the highlight. Default is <mxConstants.DEFAULT_VALID_COLOR>.
+ */
+mxConstraintHandler.prototype.highlightColor = mxConstants.DEFAULT_VALID_COLOR;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxConstraintHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxConstraintHandler.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this handler.
+ */
+mxConstraintHandler.prototype.reset = function()
+{
+ if (this.focusIcons != null)
+ {
+ for (var i = 0; i < this.focusIcons.length; i++)
+ {
+ this.focusIcons[i].destroy();
+ }
+
+ this.focusIcons = null;
+ }
+
+ if (this.focusHighlight != null)
+ {
+ this.focusHighlight.destroy();
+ this.focusHighlight = null;
+ }
+
+ this.currentConstraint = null;
+ this.currentFocusArea = null;
+ this.currentPoint = null;
+ this.currentFocus = null;
+ this.focusPoints = null;
+};
+
+/**
+ * Function: getTolerance
+ *
+ * Returns the tolerance to be used for intersecting connection points.
+ */
+mxConstraintHandler.prototype.getTolerance = function()
+{
+ return this.graph.getTolerance();
+};
+
+/**
+ * Function: getImageForConstraint
+ *
+ * Returns the tolerance to be used for intersecting connection points.
+ */
+mxConstraintHandler.prototype.getImageForConstraint = function(state, constraint, point)
+{
+ return this.pointImage;
+};
+
+/**
+ * Function: isEventIgnored
+ *
+ * Returns true if the given <mxMouseEvent> should be ignored in <update>. This
+ * implementation always returns false.
+ */
+mxConstraintHandler.prototype.isEventIgnored = function(me, source)
+{
+ return false;
+};
+
+/**
+ * Function: update
+ *
+ * Updates the state of this handler based on the given <mxMouseEvent>.
+ * Source is a boolean indicating if the cell is a source or target.
+ */
+mxConstraintHandler.prototype.update = function(me, source)
+{
+ if (this.isEnabled() && !this.isEventIgnored(me))
+ {
+ var tol = this.getTolerance();
+ var mouse = new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol);
+ var connectable = (me.getCell() != null) ? this.graph.isCellConnectable(me.getCell()) : false;
+
+ if ((this.currentFocusArea == null || (!mxUtils.intersects(this.currentFocusArea, mouse) ||
+ (me.getState() != null && this.currentFocus != null && connectable))))
+ {
+ this.currentFocusArea = null;
+
+ if (me.getState() != this.currentFocus)
+ {
+ this.currentFocus = null;
+ this.constraints = (me.getState() != null && connectable) ?
+ this.graph.getAllConnectionConstraints(me.getState(), source) : null;
+
+ // Only uses cells which have constraints
+ if (this.constraints != null)
+ {
+ this.currentFocus = me.getState();
+ this.currentFocusArea = new mxRectangle(me.getState().x, me.getState().y, me.getState().width, me.getState().height);
+
+ if (this.focusIcons != null)
+ {
+ for (var i = 0; i < this.focusIcons.length; i++)
+ {
+ this.focusIcons[i].destroy();
+ }
+
+ this.focusIcons = null;
+ this.focusPoints = null;
+ }
+
+ this.focusIcons = [];
+ this.focusPoints = [];
+
+ for (var i = 0; i < this.constraints.length; i++)
+ {
+ var cp = this.graph.getConnectionPoint(me.getState(), this.constraints[i]);
+ var img = this.getImageForConstraint(me.getState(), this.constraints[i], cp);
+
+ var src = img.src;
+ var bounds = new mxRectangle(cp.x - img.width / 2,
+ cp.y - img.height / 2, img.width, img.height);
+ var icon = new mxImageShape(bounds, src);
+ icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_SVG :
+ mxConstants.DIALECT_VML;
+ icon.init(this.graph.getView().getOverlayPane());
+
+ // Move the icon behind all other overlays
+ if (icon.node.previousSibling != null)
+ {
+ icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
+ }
+
+ var getState = mxUtils.bind(this, function()
+ {
+ return (this.currentFocus != null) ? this.currentFocus : me.getState();
+ });
+
+ icon.redraw();
+
+ mxEvent.redirectMouseEvents(icon.node, this.graph, getState);
+ this.currentFocusArea.add(icon.bounds);
+ this.focusIcons.push(icon);
+ this.focusPoints.push(cp);
+ }
+
+ this.currentFocusArea.grow(tol);
+ }
+ else if (this.focusIcons != null)
+ {
+ if (this.focusHighlight != null)
+ {
+ this.focusHighlight.destroy();
+ this.focusHighlight = null;
+ }
+
+ for (var i = 0; i < this.focusIcons.length; i++)
+ {
+ this.focusIcons[i].destroy();
+ }
+
+ this.focusIcons = null;
+ this.focusPoints = null;
+ }
+ }
+ }
+
+ this.currentConstraint = null;
+ this.currentPoint = null;
+
+ if (this.focusIcons != null && this.constraints != null &&
+ (me.getState() == null || this.currentFocus == me.getState()))
+ {
+ for (var i = 0; i < this.focusIcons.length; i++)
+ {
+ if (mxUtils.intersects(this.focusIcons[i].bounds, mouse))
+ {
+ this.currentConstraint = this.constraints[i];
+ this.currentPoint = this.focusPoints[i];
+
+ var tmp = this.focusIcons[i].bounds.clone();
+ tmp.grow((mxClient.IS_IE) ? 3 : 2);
+
+ if (mxClient.IS_IE)
+ {
+ tmp.width -= 1;
+ tmp.height -= 1;
+ }
+
+ if (this.focusHighlight == null)
+ {
+ var hl = new mxRectangleShape(tmp, null, this.highlightColor, 3);
+ hl.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_SVG :
+ mxConstants.DIALECT_VML;
+ hl.init(this.graph.getView().getOverlayPane());
+ this.focusHighlight = hl;
+
+ var getState = mxUtils.bind(this, function()
+ {
+ return (this.currentFocus != null) ? this.currentFocus : me.getState();
+ });
+
+ mxEvent.redirectMouseEvents(hl.node, this.graph, getState/*, mouseDown*/);
+ }
+ else
+ {
+ this.focusHighlight.bounds = tmp;
+ this.focusHighlight.redraw();
+ }
+
+ break;
+ }
+ }
+ }
+
+ if (this.currentConstraint == null &&
+ this.focusHighlight != null)
+ {
+ this.focusHighlight.destroy();
+ this.focusHighlight = null;
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroy this handler.
+ */
+mxConstraintHandler.prototype.destroy = function()
+{
+ this.reset();
+}; \ No newline at end of file
diff --git a/src/js/handler/mxEdgeHandler.js b/src/js/handler/mxEdgeHandler.js
new file mode 100644
index 0000000..2028342
--- /dev/null
+++ b/src/js/handler/mxEdgeHandler.js
@@ -0,0 +1,1529 @@
+/**
+ * $Id: mxEdgeHandler.js,v 1.178 2012-09-12 09:16:23 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEdgeHandler
+ *
+ * Graph event handler that reconnects edges and modifies control points and
+ * the edge label location. Uses <mxTerminalMarker> for finding and
+ * highlighting new source and target vertices. This handler is automatically
+ * created in <mxGraph.createHandler> for each selected edge.
+ *
+ * To enable adding/removing control points, the following code can be used:
+ *
+ * (code)
+ * mxEdgeHandler.prototype.addEnabled = true;
+ * mxEdgeHandler.prototype.removeEnabled = true;
+ * (end)
+ *
+ * Note: This experimental feature is not recommended for production use.
+ *
+ * Constructor: mxEdgeHandler
+ *
+ * Constructs an edge handler for the specified <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the cell to be handled.
+ */
+function mxEdgeHandler(state)
+{
+ if (state != null)
+ {
+ this.state = state;
+ this.init();
+ }
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxEdgeHandler.prototype.graph = null;
+
+/**
+ * Variable: state
+ *
+ * Reference to the <mxCellState> being modified.
+ */
+mxEdgeHandler.prototype.state = null;
+
+/**
+ * Variable: marker
+ *
+ * Holds the <mxTerminalMarker> which is used for highlighting terminals.
+ */
+mxEdgeHandler.prototype.marker = null;
+
+/**
+ * Variable: constraintHandler
+ *
+ * Holds the <mxConstraintHandler> used for drawing and highlighting
+ * constraints.
+ */
+mxEdgeHandler.prototype.constraintHandler = null;
+
+/**
+ * Variable: error
+ *
+ * Holds the current validation error while a connection is being changed.
+ */
+mxEdgeHandler.prototype.error = null;
+
+/**
+ * Variable: shape
+ *
+ * Holds the <mxShape> that represents the preview edge.
+ */
+mxEdgeHandler.prototype.shape = null;
+
+/**
+ * Variable: bends
+ *
+ * Holds the <mxShapes> that represent the points.
+ */
+mxEdgeHandler.prototype.bends = null;
+
+/**
+ * Variable: labelShape
+ *
+ * Holds the <mxShape> that represents the label position.
+ */
+mxEdgeHandler.prototype.labelShape = null;
+
+/**
+ * Variable: cloneEnabled
+ *
+ * Specifies if cloning by control-drag is enabled. Default is true.
+ */
+mxEdgeHandler.prototype.cloneEnabled = true;
+
+/**
+ * Variable: addEnabled
+ *
+ * Specifies if adding bends by shift-click is enabled. Default is false.
+ * Note: This experimental feature is not recommended for production use.
+ */
+mxEdgeHandler.prototype.addEnabled = false;
+
+/**
+ * Variable: removeEnabled
+ *
+ * Specifies if removing bends by shift-click is enabled. Default is false.
+ * Note: This experimental feature is not recommended for production use.
+ */
+mxEdgeHandler.prototype.removeEnabled = false;
+
+/**
+ * Variable: preferHtml
+ *
+ * Specifies if bends should be added to the graph container. This is updated
+ * in <init> based on whether the edge or one of its terminals has an HTML
+ * label in the container.
+ */
+mxEdgeHandler.prototype.preferHtml = false;
+
+/**
+ * Variable: allowHandleBoundsCheck
+ *
+ * Specifies if the bounds of handles should be used for hit-detection in IE
+ * Default is true.
+ */
+mxEdgeHandler.prototype.allowHandleBoundsCheck = true;
+
+/**
+ * Variable: snapToTerminals
+ *
+ * Specifies if waypoints should snap to the routing centers of terminals.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.snapToTerminals = false;
+
+/**
+ * Variable: crisp
+ *
+ * Specifies if the edge handles should be rendered in crisp mode. Default is
+ * true.
+ */
+mxEdgeHandler.prototype.crisp = true;
+
+/**
+ * Variable: handleImage
+ *
+ * Optional <mxImage> to be used as handles. Default is null.
+ */
+mxEdgeHandler.prototype.handleImage = null;
+
+/**
+ * Variable: tolerance
+ *
+ * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
+ */
+mxEdgeHandler.prototype.tolerance = 0;
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this edge handler.
+ */
+mxEdgeHandler.prototype.init = function()
+{
+ this.graph = this.state.view.graph;
+ this.marker = this.createMarker();
+ this.constraintHandler = new mxConstraintHandler(this.graph);
+
+ // Clones the original points from the cell
+ // and makes sure at least one point exists
+ this.points = [];
+
+ // Uses the absolute points of the state
+ // for the initial configuration and preview
+ this.abspoints = this.getSelectionPoints(this.state);
+ this.shape = this.createSelectionShape(this.abspoints);
+ this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.shape.init(this.graph.getView().getOverlayPane());
+ this.shape.node.style.cursor = mxConstants.CURSOR_MOVABLE_EDGE;
+
+ // Event handling
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(this.shape.node, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.dblClick(evt, this.state.cell);
+ })
+ );
+ mxEvent.addListener(this.shape.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.addEnabled && this.isAddPointEvent(evt))
+ {
+ this.addPoint(this.state, evt);
+ }
+ else
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, this.state));
+ }
+ })
+ );
+ mxEvent.addListener(this.shape.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ var cell = this.state.cell;
+
+ // Finds the cell under the mouse if the edge is being connected
+ // in which case the edge is never highlighted as it cannot
+ // be its own source or target terminal (transparent preview)
+ if (this.index != null)
+ {
+ var pt = mxUtils.convertPoint(this.graph.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ cell = this.graph.getCellAt(pt.x, pt.y);
+
+ // Swimlane content area is transparent in this case
+ if (this.graph.isSwimlane(cell) && this.graph.hitsSwimlaneContent(cell, pt.x, pt.y))
+ {
+ cell = null;
+ }
+ }
+
+ this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, this.graph.getView().getState(cell)));
+ })
+ );
+ mxEvent.addListener(this.shape.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, this.state));
+ })
+ );
+
+ // Updates preferHtml
+ this.preferHtml = this.state.text != null &&
+ this.state.text.node.parentNode == this.graph.container;
+
+ if (!this.preferHtml)
+ {
+ // Checks source terminal
+ var sourceState = this.state.getVisibleTerminalState(true);
+
+ if (sourceState != null)
+ {
+ this.preferHtml = sourceState.text != null &&
+ sourceState.text.node.parentNode == this.graph.container;
+ }
+
+ if (!this.preferHtml)
+ {
+ // Checks target terminal
+ var targetState = this.state.getVisibleTerminalState(false);
+
+ if (targetState != null)
+ {
+ this.preferHtml = targetState.text != null &&
+ targetState.text.node.parentNode == this.graph.container;
+ }
+ }
+ }
+
+ // Creates bends for the non-routed absolute points
+ // or bends that don't correspond to points
+ if (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells ||
+ mxGraphHandler.prototype.maxCells <= 0)
+ {
+ this.bends = this.createBends();
+ }
+
+ // Adds a rectangular handle for the label position
+ this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+ this.labelShape = new mxRectangleShape(new mxRectangle(),
+ mxConstants.LABEL_HANDLE_FILLCOLOR,
+ mxConstants.HANDLE_STROKECOLOR);
+ this.initBend(this.labelShape);
+ this.labelShape.node.style.cursor = mxConstants.CURSOR_LABEL_HANDLE;
+ mxEvent.redirectMouseEvents(this.labelShape.node, this.graph, this.state);
+
+ this.redraw();
+};
+
+/**
+ * Function: isAddPointEvent
+ *
+ * Returns true if the given event is a trigger to add a new point. This
+ * implementation returns true if shift is pressed.
+ */
+mxEdgeHandler.prototype.isAddPointEvent = function(evt)
+{
+ return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isRemovePointEvent
+ *
+ * Returns true if the given event is a trigger to remove a point. This
+ * implementation returns true if shift is pressed.
+ */
+mxEdgeHandler.prototype.isRemovePointEvent = function(evt)
+{
+ return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: getSelectionPoints
+ *
+ * Returns the list of points that defines the selection stroke.
+ */
+mxEdgeHandler.prototype.getSelectionPoints = function(state)
+{
+ return state.absolutePoints;
+};
+
+/**
+ * Function: createSelectionShape
+ *
+ * Creates the shape used to draw the selection border.
+ */
+mxEdgeHandler.prototype.createSelectionShape = function(points)
+{
+ var shape = new mxPolyline(points, this.getSelectionColor());
+ shape.strokewidth = this.getSelectionStrokeWidth();
+ shape.isDashed = this.isSelectionDashed();
+
+ return shape;
+};
+
+/**
+ * Function: getSelectionColor
+ *
+ * Returns <mxConstants.EDGE_SELECTION_COLOR>.
+ */
+mxEdgeHandler.prototype.getSelectionColor = function()
+{
+ return mxConstants.EDGE_SELECTION_COLOR;
+};
+
+/**
+ * Function: getSelectionStrokeWidth
+ *
+ * Returns <mxConstants.EDGE_SELECTION_STROKEWIDTH>.
+ */
+mxEdgeHandler.prototype.getSelectionStrokeWidth = function()
+{
+ return mxConstants.EDGE_SELECTION_STROKEWIDTH;
+};
+
+/**
+ * Function: isSelectionDashed
+ *
+ * Returns <mxConstants.EDGE_SELECTION_DASHED>.
+ */
+mxEdgeHandler.prototype.isSelectionDashed = function()
+{
+ return mxConstants.EDGE_SELECTION_DASHED;
+};
+
+/**
+ * Function: isConnectableCell
+ *
+ * Returns true if the given cell is connectable. This is a hook to
+ * disable floating connections. This implementation returns true.
+ */
+mxEdgeHandler.prototype.isConnectableCell = function(cell)
+{
+ return true;
+};
+
+/**
+ * Function: createMarker
+ *
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxEdgeHandler.prototype.createMarker = function()
+{
+ var marker = new mxCellMarker(this.graph);
+ var self = this; // closure
+
+ // Only returns edges if they are connectable and never returns
+ // the edge that is currently being modified
+ marker.getCell = function(me)
+ {
+ var cell = mxCellMarker.prototype.getCell.apply(this, arguments);
+
+ if (!self.isConnectableCell(cell))
+ {
+ return null;
+ }
+
+ var model = self.graph.getModel();
+
+ if (cell == self.state.cell || (cell != null &&
+ !self.graph.connectableEdges && model.isEdge(cell)))
+ {
+ cell = null;
+ }
+
+ return cell;
+ };
+
+ // Sets the highlight color according to validateConnection
+ marker.isValidState = function(state)
+ {
+ var model = self.graph.getModel();
+ var other = self.graph.view.getTerminalPort(state,
+ self.graph.view.getState(model.getTerminal(self.state.cell,
+ !self.isSource)), !self.isSource);
+ var otherCell = (other != null) ? other.cell : null;
+ var source = (self.isSource) ? state.cell : otherCell;
+ var target = (self.isSource) ? otherCell : state.cell;
+
+ // Updates the error message of the handler
+ self.error = self.validateConnection(source, target);
+
+ return self.error == null;
+ };
+
+ return marker;
+};
+
+/**
+ * Function: validateConnection
+ *
+ * Returns the error message or an empty string if the connection for the
+ * given source, target pair is not valid. Otherwise it returns null. This
+ * implementation uses <mxGraph.getEdgeValidationError>.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxEdgeHandler.prototype.validateConnection = function(source, target)
+{
+ return this.graph.getEdgeValidationError(this.state.cell, source, target);
+};
+
+/**
+ * Function: createBends
+ *
+ * Creates and returns the bends used for modifying the edge. This is
+ * typically an array of <mxRectangleShapes>.
+ */
+ mxEdgeHandler.prototype.createBends = function()
+ {
+ var cell = this.state.cell;
+ var bends = [];
+
+ for (var i = 0; i < this.abspoints.length; i++)
+ {
+ if (this.isHandleVisible(i))
+ {
+ var source = i == 0;
+ var target = i == this.abspoints.length - 1;
+ var terminal = source || target;
+
+ if (terminal || this.graph.isCellBendable(cell))
+ {
+ var bend = this.createHandleShape(i);
+ this.initBend(bend);
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+
+ if (this.isHandleEnabled(i))
+ {
+ if (mxClient.IS_TOUCH)
+ {
+ var getState = mxUtils.bind(this, function(evt)
+ {
+ var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ return this.graph.view.getState(this.graph.getCellAt(pt.x, pt.y));
+ });
+
+ mxEvent.redirectMouseEvents(bend.node, this.graph, getState);
+ }
+ else
+ {
+ bend.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state);
+ }
+ }
+
+ bends.push(bend);
+
+ if (!terminal)
+ {
+ this.points.push(new mxPoint(0,0));
+ bend.node.style.visibility = 'hidden';
+ }
+ }
+ }
+ }
+
+ return bends;
+};
+/**
+ * Function: isHandleEnabled
+ *
+ * Creates the shape used to display the given bend.
+ */
+mxEdgeHandler.prototype.isHandleEnabled = function(index)
+{
+ return true;
+};
+
+/**
+ * Function: isHandleVisible
+ *
+ * Returns true if the handle at the given index is visible.
+ */
+mxEdgeHandler.prototype.isHandleVisible = function(index)
+{
+ return true;
+};
+
+/**
+ * Function: createHandleShape
+ *
+ * Creates the shape used to display the given bend. Note that the index may be
+ * null for special cases, such as when called from
+ * <mxElbowEdgeHandler.createVirtualBend>.
+ */
+mxEdgeHandler.prototype.createHandleShape = function(index)
+{
+ if (this.handleImage != null)
+ {
+ return new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src);
+ }
+ else
+ {
+ var s = mxConstants.HANDLE_SIZE;
+
+ if (this.preferHtml)
+ {
+ s -= 1;
+ }
+
+ return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+ }
+};
+
+/**
+ * Function: initBend
+ *
+ * Helper method to initialize the given bend.
+ *
+ * Parameters:
+ *
+ * bend - <mxShape> that represents the bend to be initialized.
+ */
+mxEdgeHandler.prototype.initBend = function(bend)
+{
+ bend.crisp = this.crisp;
+
+ if (this.preferHtml)
+ {
+ bend.dialect = mxConstants.DIALECT_STRICTHTML;
+ bend.init(this.graph.container);
+ }
+ else
+ {
+ bend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ bend.init(this.graph.getView().getOverlayPane());
+ }
+};
+
+/**
+ * Function: getHandleForEvent
+ *
+ * Returns the index of the handle for the given event.
+ */
+mxEdgeHandler.prototype.getHandleForEvent = function(me)
+{
+ // Finds the handle that triggered the event
+ if (this.bends != null)
+ {
+ // Connection highlight may consume events before they reach sizer handle
+ var tol = this.tolerance;
+ var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+ new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+
+ for (var i = 0; i < this.bends.length; i++)
+ {
+ if (me.isSource(this.bends[i]) || (hit != null &&
+ this.bends[i].node.style.visibility != 'hidden' &&
+ mxUtils.intersects(this.bends[i].bounds, hit)))
+ {
+ return i;
+ }
+ }
+ }
+
+ if (me.isSource(this.labelShape) || me.isSource(this.state.text))
+ {
+ // Workaround for SELECT element not working in Webkit
+ if ((!mxClient.IS_SF && !mxClient.IS_GC) || me.getSource().nodeName != 'SELECT')
+ {
+ return mxEvent.LABEL_HANDLE;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by checking if a special element of the handler
+ * was clicked, in which case the index parameter is non-null. The
+ * indices may be one of <LABEL_HANDLE> or the number of the respective
+ * control point. The source and target points are used for reconnecting
+ * the edge.
+ */
+mxEdgeHandler.prototype.mouseDown = function(sender, me)
+{
+ var handle = null;
+
+ // Handles the case where the state in the event points to another
+ // cell if the cell has a HTML label which sits on top of the handles
+ // NOTE: Commented out. This should not be required as all HTML labels
+ // are in order an do not appear behind the handles.
+ //if (mxClient.IS_SVG || me.getState() == this.state)
+ {
+ handle = this.getHandleForEvent(me);
+ }
+
+ if (handle != null && !me.isConsumed() && this.graph.isEnabled() &&
+ !this.graph.isForceMarqueeEvent(me.getEvent()))
+ {
+ if (this.removeEnabled && this.isRemovePointEvent(me.getEvent()))
+ {
+ this.removePoint(this.state, handle);
+ }
+ else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell()))
+ {
+ this.start(me.getX(), me.getY(), handle);
+ }
+
+ me.consume();
+ }
+};
+
+/**
+ * Function: start
+ *
+ * Starts the handling of the mouse gesture.
+ */
+mxEdgeHandler.prototype.start = function(x, y, index)
+{
+ this.startX = x;
+ this.startY = y;
+
+ this.isSource = (this.bends == null) ? false : index == 0;
+ this.isTarget = (this.bends == null) ? false : index == this.bends.length - 1;
+ this.isLabel = index == mxEvent.LABEL_HANDLE;
+
+ if (this.isSource || this.isTarget)
+ {
+ var cell = this.state.cell;
+ var terminal = this.graph.model.getTerminal(cell, this.isSource);
+
+ if ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) ||
+ (terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource)))
+ {
+ this.index = index;
+ }
+ }
+ else
+ {
+ this.index = index;
+ }
+};
+
+/**
+ * Function: clonePreviewState
+ *
+ * Returns a clone of the current preview state for the given point and terminal.
+ */
+mxEdgeHandler.prototype.clonePreviewState = function(point, terminal)
+{
+ return this.state.clone();
+};
+
+/**
+ * Function: getSnapToTerminalTolerance
+ *
+ * Returns the tolerance for the guides. Default value is
+ * gridSize * scale / 2.
+ */
+mxEdgeHandler.prototype.getSnapToTerminalTolerance = function()
+{
+ return this.graph.gridSize * this.graph.view.scale / 2;
+};
+
+/**
+ * Function: getPointForEvent
+ *
+ * Returns the point for the given event.
+ */
+mxEdgeHandler.prototype.getPointForEvent = function(me)
+{
+ var point = new mxPoint(me.getGraphX(), me.getGraphY());
+
+ var tt = this.getSnapToTerminalTolerance();
+ var view = this.graph.getView();
+ var overrideX = false;
+ var overrideY = false;
+
+ if (this.snapToTerminals && tt > 0)
+ {
+ function snapToPoint(pt)
+ {
+ if (pt != null)
+ {
+ var x = pt.x;
+
+ if (Math.abs(point.x - x) < tt)
+ {
+ point.x = x;
+ overrideX = true;
+ }
+
+ var y = pt.y;
+
+ if (Math.abs(point.y - y) < tt)
+ {
+ point.y = y;
+ overrideY = true;
+ }
+ }
+ }
+
+ // Temporary function
+ function snapToTerminal(terminal)
+ {
+ if (terminal != null)
+ {
+ snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal),
+ view.getRoutingCenterY(terminal)));
+ }
+ };
+
+ snapToTerminal.call(this, this.state.getVisibleTerminalState(true));
+ snapToTerminal.call(this, this.state.getVisibleTerminalState(false));
+
+ if (this.abspoints != null)
+ {
+ for (var i = 0; i < this.abspoints; i++)
+ {
+ if (i != this.index)
+ {
+ snapToPoint.call(this, this.abspoints[i]);
+ }
+ }
+ }
+ }
+
+ if (this.graph.isGridEnabledEvent(me.getEvent()))
+ {
+ var scale = view.scale;
+ var tr = view.translate;
+
+ if (!overrideX)
+ {
+ point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
+ }
+
+ if (!overrideY)
+ {
+ point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
+ }
+ }
+
+ return point;
+};
+
+/**
+ * Function: getPreviewTerminalState
+ *
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.getPreviewTerminalState = function(me)
+{
+ this.constraintHandler.update(me, this.isSource);
+ this.marker.process(me);
+ var currentState = this.marker.getValidState();
+ var result = null;
+
+ if (this.constraintHandler.currentFocus != null &&
+ this.constraintHandler.currentConstraint != null)
+ {
+ this.marker.reset();
+ }
+
+ if (currentState != null)
+ {
+ result = currentState;
+ }
+ else if (this.constraintHandler.currentConstraint != null &&
+ this.constraintHandler.currentFocus != null)
+ {
+ result = this.constraintHandler.currentFocus;
+ }
+
+ return result;
+};
+
+/**
+ * Function: getPreviewPoints
+ *
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.getPreviewPoints = function(point)
+{
+ var geometry = this.graph.getCellGeometry(this.state.cell);
+ var points = (geometry.points != null) ? geometry.points.slice() : null;
+
+ if (!this.isSource && !this.isTarget)
+ {
+ this.convertPoint(point, false);
+
+ if (points == null)
+ {
+ points = [point];
+ }
+ else
+ {
+ points[this.index - 1] = point;
+ }
+ }
+ else if (this.graph.resetEdgesOnConnect)
+ {
+ points = null;
+ }
+
+ return points;
+};
+
+/**
+ * Function: updatePreviewState
+ *
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState)
+{
+ // Computes the points for the edge style and terminals
+ var sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true);
+ var targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false);
+
+ var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true);
+ var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false);
+
+ var constraint = this.constraintHandler.currentConstraint;
+
+ if (constraint == null)
+ {
+ constraint = new mxConnectionConstraint();
+ }
+
+ if (this.isSource)
+ {
+ sourceConstraint = constraint;
+ }
+ else if (this.isTarget)
+ {
+ targetConstraint = constraint;
+ }
+
+ if (!this.isSource || sourceState != null)
+ {
+ edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint);
+ }
+
+ if (!this.isTarget || targetState != null)
+ {
+ edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint);
+ }
+
+ if ((this.isSource || this.isTarget) && terminalState == null)
+ {
+ edge.setAbsoluteTerminalPoint(point, this.isSource);
+
+ if (this.marker.getMarkedState() == null)
+ {
+ this.error = (this.graph.allowDanglingEdges) ? null : '';
+ }
+ }
+
+ edge.view.updatePoints(edge, this.points, sourceState, targetState);
+ edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState);
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating the preview.
+ */
+mxEdgeHandler.prototype.mouseMove = function(sender, me)
+{
+ if (this.index != null && this.marker != null)
+ {
+ var point = this.getPointForEvent(me);
+
+ if (this.isLabel)
+ {
+ this.label.x = point.x;
+ this.label.y = point.y;
+ }
+ else
+ {
+ this.points = this.getPreviewPoints(point);
+ var terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null;
+ var clone = this.clonePreviewState(point, (terminalState != null) ? terminalState.cell : null);
+ this.updatePreviewState(clone, point, terminalState);
+
+ // Sets the color of the preview to valid or invalid, updates the
+ // points of the preview and redraws
+ var color = (this.error == null) ? this.marker.validColor :
+ this.marker.invalidColor;
+ this.setPreviewColor(color);
+ this.abspoints = clone.absolutePoints;
+ this.active = true;
+ }
+
+ this.drawPreview();
+ mxEvent.consume(me.getEvent());
+ me.consume();
+ }
+ // Workaround for disabling the connect highlight when over handle
+ else if (mxClient.IS_IE && this.getHandleForEvent(me) != null)
+ {
+ me.consume(false);
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event to applying the previewed changes on the edge by
+ * using <moveLabel>, <connect> or <changePoints>.
+ */
+mxEdgeHandler.prototype.mouseUp = function(sender, me)
+{
+ if (this.index != null && this.marker != null)
+ {
+ var edge = this.state.cell;
+
+ // Ignores event if mouse has not been moved
+ if (me.getX() != this.startX || me.getY() != this.startY)
+ {
+ // Displays the reason for not carriying out the change
+ // if there is an error message with non-zero length
+ if (this.error != null)
+ {
+ if (this.error.length > 0)
+ {
+ this.graph.validationAlert(this.error);
+ }
+ }
+ else if (this.isLabel)
+ {
+ this.moveLabel(this.state, this.label.x, this.label.y);
+ }
+ else if (this.isSource || this.isTarget)
+ {
+ var terminal = null;
+
+ if (this.constraintHandler.currentConstraint != null &&
+ this.constraintHandler.currentFocus != null)
+ {
+ terminal = this.constraintHandler.currentFocus.cell;
+ }
+
+ if (terminal == null && this.marker.hasValidState())
+ {
+ terminal = this.marker.validState.cell;
+ }
+
+ if (terminal != null)
+ {
+ edge = this.connect(edge, terminal, this.isSource,
+ this.graph.isCloneEvent(me.getEvent()) && this.cloneEnabled &&
+ this.graph.isCellsCloneable(), me);
+ }
+ else if (this.graph.isAllowDanglingEdges())
+ {
+ var pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1];
+ pt.x = pt.x / this.graph.view.scale - this.graph.view.translate.x;
+ pt.y = pt.y / this.graph.view.scale - this.graph.view.translate.y;
+
+ var pstate = this.graph.getView().getState(
+ this.graph.getModel().getParent(edge));
+
+ if (pstate != null)
+ {
+ pt.x -= pstate.origin.x;
+ pt.y -= pstate.origin.y;
+ }
+
+ pt.x -= this.graph.panDx / this.graph.view.scale;
+ pt.y -= this.graph.panDy / this.graph.view.scale;
+
+ // Destroys and rectreates this handler
+ this.changeTerminalPoint(edge, pt, this.isSource);
+ }
+ }
+ else if (this.active)
+ {
+ this.changePoints(edge, this.points);
+ }
+ else
+ {
+ this.graph.getView().invalidate(this.state.cell);
+ this.graph.getView().revalidate(this.state.cell);
+ }
+ }
+
+ // Resets the preview color the state of the handler if this
+ // handler has not been recreated
+ if (this.marker != null)
+ {
+ this.reset();
+
+ // Updates the selection if the edge has been cloned
+ if (edge != this.state.cell)
+ {
+ this.graph.setSelectionCell(edge);
+ }
+ }
+
+ me.consume();
+ }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this handler.
+ */
+mxEdgeHandler.prototype.reset = function()
+{
+ this.error = null;
+ this.index = null;
+ this.label = null;
+ this.points = null;
+ this.active = false;
+ this.isLabel = false;
+ this.isSource = false;
+ this.isTarget = false;
+ this.marker.reset();
+ this.constraintHandler.reset();
+ this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);
+ this.redraw();
+};
+
+/**
+ * Function: setPreviewColor
+ *
+ * Sets the color of the preview to the given value.
+ */
+mxEdgeHandler.prototype.setPreviewColor = function(color)
+{
+ if (this.shape != null && this.shape.node != null)
+ {
+ if (this.shape.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.shape.innerNode.setAttribute('stroke', color);
+ }
+ else
+ {
+ this.shape.node.strokecolor = color;
+ }
+ }
+};
+
+/**
+ * Function: convertPoint
+ *
+ * Converts the given point in-place from screen to unscaled, untranslated
+ * graph coordinates and applies the grid. Returns the given, modified
+ * point instance.
+ *
+ * Parameters:
+ *
+ * point - <mxPoint> to be converted.
+ * gridEnabled - Boolean that specifies if the grid should be applied.
+ */
+mxEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
+{
+ var scale = this.graph.getView().getScale();
+ var tr = this.graph.getView().getTranslate();
+
+ if (gridEnabled)
+ {
+ point.x = this.graph.snap(point.x);
+ point.y = this.graph.snap(point.y);
+ }
+
+ point.x = Math.round(point.x / scale - tr.x);
+ point.y = Math.round(point.y / scale - tr.y);
+
+ var pstate = this.graph.getView().getState(
+ this.graph.getModel().getParent(this.state.cell));
+
+ if (pstate != null)
+ {
+ point.x -= pstate.origin.x;
+ point.y -= pstate.origin.y;
+ }
+
+ return point;
+};
+
+/**
+ * Function: moveLabel
+ *
+ * Changes the coordinates for the label of the given edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge.
+ * x - Integer that specifies the x-coordinate of the new location.
+ * y - Integer that specifies the y-coordinate of the new location.
+ */
+mxEdgeHandler.prototype.moveLabel = function(edgeState, x, y)
+{
+ var model = this.graph.getModel();
+ var geometry = model.getGeometry(edgeState.cell);
+
+ if (geometry != null)
+ {
+ geometry = geometry.clone();
+
+ // Resets the relative location stored inside the geometry
+ var pt = this.graph.getView().getRelativePoint(edgeState, x, y);
+ geometry.x = pt.x;
+ geometry.y = pt.y;
+
+ // Resets the offset inside the geometry to find the offset
+ // from the resulting point
+ var scale = this.graph.getView().scale;
+ geometry.offset = new mxPoint(0, 0);
+ var pt = this.graph.view.getPoint(edgeState, geometry);
+ geometry.offset = new mxPoint((x - pt.x) / scale, (y - pt.y) / scale);
+
+ model.setGeometry(edgeState.cell, geometry);
+ }
+};
+
+/**
+ * Function: connect
+ *
+ * Changes the terminal or terminal point of the given edge in the graph
+ * model.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to be reconnected.
+ * terminal - <mxCell> that represents the new terminal.
+ * isSource - Boolean indicating if the new terminal is the source or
+ * target terminal.
+ * isClone - Boolean indicating if the new connection should be a clone of
+ * the old edge.
+ * me - <mxMouseEvent> that contains the mouse up event.
+ */
+mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
+{
+ var model = this.graph.getModel();
+ var parent = model.getParent(edge);
+
+ model.beginUpdate();
+ try
+ {
+ // Clones and adds the cell
+ if (isClone)
+ {
+ var clone = edge.clone();
+ model.add(parent, clone, model.getChildCount(parent));
+
+ var other = model.getTerminal(edge, !isSource);
+ this.graph.connectCell(clone, other, !isSource);
+
+ edge = clone;
+ }
+
+ var constraint = this.constraintHandler.currentConstraint;
+
+ if (constraint == null)
+ {
+ constraint = new mxConnectionConstraint();
+ }
+
+ this.graph.connectCell(edge, terminal, isSource, constraint);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+
+ return edge;
+};
+
+/**
+ * Function: changeTerminalPoint
+ *
+ * Changes the terminal point of the given edge.
+ */
+mxEdgeHandler.prototype.changeTerminalPoint = function(edge, point, isSource)
+{
+ var model = this.graph.getModel();
+ var geo = model.getGeometry(edge);
+
+ if (geo != null)
+ {
+ model.beginUpdate();
+ try
+ {
+ geo = geo.clone();
+ geo.setTerminalPoint(point, isSource);
+ model.setGeometry(edge, geo);
+ this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint());
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: changePoints
+ *
+ * Changes the control points of the given edge in the graph model.
+ */
+mxEdgeHandler.prototype.changePoints = function(edge, points)
+{
+ var model = this.graph.getModel();
+ var geo = model.getGeometry(edge);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ geo.points = points;
+
+ model.setGeometry(edge, geo);
+ }
+};
+
+/**
+ * Function: addPoint
+ *
+ * Adds a control point for the given state and event.
+ */
+mxEdgeHandler.prototype.addPoint = function(state, evt)
+{
+ var geo = this.graph.getCellGeometry(state.cell);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt),
+ mxEvent.getClientY(evt));
+ var index = mxUtils.findNearestSegment(state, pt.x, pt.y);
+ var gridEnabled = this.graph.isGridEnabledEvent(evt);
+ this.convertPoint(pt, gridEnabled);
+
+ if (geo.points == null)
+ {
+ geo.points = [pt];
+ }
+ else
+ {
+ geo.points.splice(index, 0, pt);
+ }
+
+ this.graph.getModel().setGeometry(state.cell, geo);
+ this.destroy();
+ this.init();
+ mxEvent.consume(evt);
+ }
+};
+
+/**
+ * Function: removePoint
+ *
+ * Removes the control point at the given index from the given state.
+ */
+mxEdgeHandler.prototype.removePoint = function(state, index)
+{
+ if (index > 0 && index < this.abspoints.length - 1)
+ {
+ var geo = this.graph.getCellGeometry(this.state.cell);
+
+ if (geo != null &&
+ geo.points != null)
+ {
+ geo = geo.clone();
+ geo.points.splice(index - 1, 1);
+ this.graph.getModel().setGeometry(state.cell, geo);
+ this.destroy();
+ this.init();
+ }
+ }
+};
+
+/**
+ * Function: getHandleFillColor
+ *
+ * Returns the fillcolor for the handle at the given index.
+ */
+mxEdgeHandler.prototype.getHandleFillColor = function(index)
+{
+ var isSource = index == 0;
+ var cell = this.state.cell;
+ var terminal = this.graph.getModel().getTerminal(cell, isSource);
+ var color = mxConstants.HANDLE_FILLCOLOR;
+
+ if ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) ||
+ (terminal == null && !this.graph.isTerminalPointMovable(cell, isSource)))
+ {
+ color = mxConstants.LOCKED_HANDLE_FILLCOLOR;
+ }
+ else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource))
+ {
+ color = mxConstants.CONNECT_HANDLE_FILLCOLOR;
+ }
+
+ return color;
+};
+
+/**
+ * Function: redraw
+ *
+ * Redraws the preview, and the bends- and label control points.
+ */
+mxEdgeHandler.prototype.redraw = function()
+{
+ this.abspoints = this.state.absolutePoints.slice();
+ var cell = this.state.cell;
+
+ // Updates the handle for the label position
+ var s = mxConstants.LABEL_HANDLE_SIZE;
+
+ this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+ this.labelShape.bounds = new mxRectangle(this.label.x - s / 2,
+ this.label.y - s / 2, s, s);
+ this.labelShape.redraw();
+
+ // Shows or hides the label handle depending on the label
+ var lab = this.graph.getLabel(cell);
+
+ if (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell))
+ {
+ this.labelShape.node.style.visibility = 'visible';
+ }
+ else
+ {
+ this.labelShape.node.style.visibility = 'hidden';
+ }
+
+ if (this.bends != null && this.bends.length > 0)
+ {
+ var n = this.abspoints.length - 1;
+
+ var p0 = this.abspoints[0];
+ var x0 = this.abspoints[0].x;
+ var y0 = this.abspoints[0].y;
+
+ var b = this.bends[0].bounds;
+ this.bends[0].bounds = new mxRectangle(x0 - b.width / 2, y0 - b.height / 2, b.width, b.height);
+ this.bends[0].fill = this.getHandleFillColor(0);
+ this.bends[0].reconfigure();
+ this.bends[0].redraw();
+
+ var pe = this.abspoints[n];
+ var xn = this.abspoints[n].x;
+ var yn = this.abspoints[n].y;
+
+ var bn = this.bends.length - 1;
+ b = this.bends[bn].bounds;
+ this.bends[bn].bounds = new mxRectangle(xn - b.width / 2, yn - b.height / 2, b.width, b.height);
+ this.bends[bn].fill = this.getHandleFillColor(bn);
+ this.bends[bn].reconfigure();
+ this.bends[bn].redraw();
+
+ this.redrawInnerBends(p0, pe);
+ }
+
+ this.drawPreview();
+};
+
+/**
+ * Function: redrawInnerBends
+ *
+ * Updates and redraws the inner bends.
+ *
+ * Parameters:
+ *
+ * p0 - <mxPoint> that represents the location of the first point.
+ * pe - <mxPoint> that represents the location of the last point.
+ */
+mxEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+ var g = this.graph.getModel().getGeometry(this.state.cell);
+ var pts = g.points;
+
+ if (pts != null)
+ {
+ if (this.points == null)
+ {
+ this.points = [];
+ }
+
+ for (var i = 1; i < this.bends.length-1; i++)
+ {
+ if (this.bends[i] != null)
+ {
+ if (this.abspoints[i] != null)
+ {
+ var x = this.abspoints[i].x;
+ var y = this.abspoints[i].y;
+
+ var b = this.bends[i].bounds;
+ this.bends[i].node.style.visibility = 'visible';
+ this.bends[i].bounds = new mxRectangle(x - b.width / 2, y - b.height / 2, b.width, b.height);
+ this.bends[i].redraw();
+
+ this.points[i - 1] = pts[i - 1];
+ }
+ else
+ {
+ this.bends[i].destroy();
+ this.bends[i] = null;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: drawPreview
+ *
+ * Redraws the preview.
+ */
+mxEdgeHandler.prototype.drawPreview = function()
+{
+ if (this.isLabel)
+ {
+ var s = mxConstants.LABEL_HANDLE_SIZE;
+
+ var bounds = new mxRectangle(this.label.x - s / 2, this.label.y - s / 2, s, s);
+ this.labelShape.bounds = bounds;
+ this.labelShape.redraw();
+ }
+ else
+ {
+ this.shape.points = this.abspoints;
+ this.shape.redraw();
+ }
+
+ // Workaround to force a repaint in AppleWebKit
+ mxUtils.repaintGraph(this.graph, this.shape.points[this.shape.points.length - 1]);
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes. This does
+ * normally not need to be called as handlers are destroyed automatically
+ * when the corresponding cell is deselected.
+ */
+mxEdgeHandler.prototype.destroy = function()
+{
+ if (this.marker != null)
+ {
+ this.marker.destroy();
+ this.marker = null;
+ }
+
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+
+ if (this.labelShape != null)
+ {
+ this.labelShape.destroy();
+ this.labelShape = null;
+ }
+
+ if (this.constraintHandler != null)
+ {
+ this.constraintHandler.destroy();
+ this.constraintHandler = null;
+ }
+
+ // Destroy the control points for the bends
+ if (this.bends != null)
+ {
+ for (var i = 0; i < this.bends.length; i++)
+ {
+ if (this.bends[i] != null)
+ {
+ this.bends[i].destroy();
+ this.bends[i] = null;
+ }
+ }
+ }
+};
diff --git a/src/js/handler/mxEdgeSegmentHandler.js b/src/js/handler/mxEdgeSegmentHandler.js
new file mode 100644
index 0000000..e14fde0
--- /dev/null
+++ b/src/js/handler/mxEdgeSegmentHandler.js
@@ -0,0 +1,284 @@
+/**
+ * $Id: mxEdgeSegmentHandler.js,v 1.14 2012-12-17 13:22:49 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+function mxEdgeSegmentHandler(state)
+{
+ if (state != null)
+ {
+ this.state = state;
+ this.init();
+ }
+};
+
+/**
+ * Extends mxEdgeHandler.
+ */
+mxEdgeSegmentHandler.prototype = new mxElbowEdgeHandler();
+mxEdgeSegmentHandler.prototype.constructor = mxEdgeSegmentHandler;
+
+/**
+ * Function: getPreviewPoints
+ *
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeSegmentHandler.prototype.getPreviewPoints = function(point)
+{
+ if (this.isSource || this.isTarget)
+ {
+ return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments);
+ }
+ else
+ {
+ this.convertPoint(point, false);
+ var pts = this.state.absolutePoints;
+ var last = pts[0].clone();
+ this.convertPoint(last, false);
+ var result = [];
+
+ for (var i = 1; i < pts.length; i++)
+ {
+ var pt = pts[i].clone();
+ this.convertPoint(pt, false);
+
+ if (i == this.index)
+ {
+ if (last.x == pt.x)
+ {
+ last.x = point.x;
+ pt.x = point.x;
+ }
+ else
+ {
+ last.y = point.y;
+ pt.y = point.y;
+ }
+ }
+
+ if (i < pts.length - 1)
+ {
+ result.push(pt);
+ }
+
+ last = pt;
+ }
+
+ if (result.length == 1)
+ {
+ var view = this.state.view;
+ var source = this.state.getVisibleTerminalState(true);
+ var target = this.state.getVisibleTerminalState(false);
+
+ if (target != null & source != null)
+ {
+ var dx = this.state.origin.x;
+ var dy = this.state.origin.y;
+
+ if (mxUtils.contains(target, result[0].x + dx, result[0].y + dy))
+ {
+ if (pts[1].y == pts[2].y)
+ {
+ result[0].y = view.getRoutingCenterY(source) - dy;
+ }
+ else
+ {
+ result[0].x = view.getRoutingCenterX(source) - dx;
+ }
+ }
+ else if (mxUtils.contains(source, result[0].x + dx, result[0].y + dy))
+ {
+ if (pts[1].y == pts[0].y)
+ {
+ result[0].y = view.getRoutingCenterY(target) - dy;
+ }
+ else
+ {
+ result[0].x = view.getRoutingCenterX(target) - dx;
+ }
+ }
+ }
+ }
+ else if (result.length == 0)
+ {
+ result = [point];
+ }
+
+ return result;
+ }
+};
+
+/**
+ * Function: createBends
+ *
+ * Adds custom bends for the center of each segment.
+ */
+mxEdgeSegmentHandler.prototype.createBends = function()
+{
+ var bends = [];
+
+ // Source
+ var bend = this.createHandleShape(0);
+
+ this.initBend(bend);
+ bend.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state);
+ bends.push(bend);
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+
+ var pts = this.state.absolutePoints;
+
+ // Waypoints (segment handles)
+ if (this.graph.isCellBendable(this.state.cell))
+ {
+ if (this.points == null)
+ {
+ this.points = [];
+ }
+
+ for (var i = 0; i < pts.length - 1; i++)
+ {
+ var bend = this.createVirtualBend();
+ bends.push(bend);
+ var horizontal = pts[i].x - pts[i + 1].x == 0;
+ bend.node.style.cursor = (horizontal) ? 'col-resize' : 'row-resize';
+ this.points.push(new mxPoint(0,0));
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+ }
+ }
+
+ // Target
+ var bend = this.createHandleShape(pts.length);
+
+ this.initBend(bend);
+ bend.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state);
+ bends.push(bend);
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+
+ return bends;
+};
+
+
+/**
+ * Function: redrawInnerBends
+ *
+ * Updates the position of the custom bends.
+ */
+mxEdgeSegmentHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+ if (this.graph.isCellBendable(this.state.cell))
+ {
+ var s = mxConstants.HANDLE_SIZE;
+ var pts = this.state.absolutePoints;
+
+ if (pts != null && pts.length > 1)
+ {
+ for (var i = 0; i < this.state.absolutePoints.length - 1; i++)
+ {
+ if (this.bends[i + 1] != null)
+ {
+ var p0 = pts[i];
+ var pe = pts[i + 1];
+ var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
+ this.bends[i+1].bounds = new mxRectangle(pt.x - s / 2, pt.y - s / 2, s, s);
+ this.bends[i+1].reconfigure();
+ this.bends[i+1].redraw();
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: connect
+ *
+ * Calls <refresh> after <mxEdgeHandler.connect>.
+ */
+mxEdgeSegmentHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
+{
+ mxEdgeHandler.prototype.connect.apply(this, arguments);
+ this.refresh();
+};
+
+/**
+ * Function: changeTerminalPoint
+ *
+ * Calls <refresh> after <mxEdgeHandler.changeTerminalPoint>.
+ */
+mxEdgeSegmentHandler.prototype.changeTerminalPoint = function(edge, point, isSource)
+{
+ mxEdgeHandler.prototype.changeTerminalPoint.apply(this, arguments);
+ this.refresh();
+};
+
+/**
+ * Function: changePoints
+ *
+ * Changes the points of the given edge to reflect the current state of the handler.
+ */
+mxEdgeSegmentHandler.prototype.changePoints = function(edge, points)
+{
+ points = [];
+ var pts = this.abspoints;
+
+ if (pts.length > 1)
+ {
+ var pt0 = pts[0];
+ var pt1 = pts[1];
+
+ for (var i = 2; i < pts.length; i++)
+ {
+ var pt2 = pts[i];
+
+ if ((Math.round(pt0.x) != Math.round(pt1.x) ||
+ Math.round(pt1.x) != Math.round(pt2.x)) &&
+ (Math.round(pt0.y) != Math.round(pt1.y) ||
+ Math.round(pt1.y) != Math.round(pt2.y)))
+ {
+ pt0 = pt1;
+ pt1 = pt1.clone();
+ this.convertPoint(pt1, false);
+ points.push(pt1);
+ }
+
+ pt1 = pt2;
+ }
+ }
+
+ mxElbowEdgeHandler.prototype.changePoints.apply(this, arguments);
+ this.refresh();
+};
+
+/**
+ * Function: refresh
+ *
+ * Refreshes the bends of this handler.
+ */
+mxEdgeSegmentHandler.prototype.refresh = function()
+{
+ if (this.bends != null)
+ {
+ for (var i = 0; i < this.bends.length; i++)
+ {
+ if (this.bends[i] != null)
+ {
+ this.bends[i].destroy();
+ this.bends[i] = null;
+ }
+ }
+
+ this.bends = this.createBends();
+ }
+};
diff --git a/src/js/handler/mxElbowEdgeHandler.js b/src/js/handler/mxElbowEdgeHandler.js
new file mode 100644
index 0000000..85fbb06
--- /dev/null
+++ b/src/js/handler/mxElbowEdgeHandler.js
@@ -0,0 +1,248 @@
+/**
+ * $Id: mxElbowEdgeHandler.js,v 1.43 2012-01-06 13:06:01 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxElbowEdgeHandler
+ *
+ * Graph event handler that reconnects edges and modifies control points and
+ * the edge label location. Uses <mxTerminalMarker> for finding and
+ * highlighting new source and target vertices. This handler is automatically
+ * created in <mxGraph.createHandler>. It extends <mxEdgeHandler>.
+ *
+ * Constructor: mxEdgeHandler
+ *
+ * Constructs an edge handler for the specified <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the cell to be modified.
+ */
+function mxElbowEdgeHandler(state)
+{
+ if (state != null)
+ {
+ this.state = state;
+ this.init();
+ }
+};
+
+/**
+ * Extends mxEdgeHandler.
+ */
+mxElbowEdgeHandler.prototype = new mxEdgeHandler();
+mxElbowEdgeHandler.prototype.constructor = mxElbowEdgeHandler;
+
+/**
+ * Specifies if a double click on the middle handle should call
+ * <mxGraph.flipEdge>. Default is true.
+ */
+mxElbowEdgeHandler.prototype.flipEnabled = true;
+
+/**
+ * Variable: doubleClickOrientationResource
+ *
+ * Specifies the resource key for the tooltip to be displayed on the single
+ * control point for routed edges. If the resource for this key does not
+ * exist then the value is used as the error message. Default is
+ * 'doubleClickOrientation'.
+ */
+mxElbowEdgeHandler.prototype.doubleClickOrientationResource =
+ (mxClient.language != 'none') ? 'doubleClickOrientation' : '';
+
+/**
+ * Function: createBends
+ *
+ * Overrides <mxEdgeHandler.createBends> to create custom bends.
+ */
+ mxElbowEdgeHandler.prototype.createBends = function()
+ {
+ var bends = [];
+
+ // Source
+ var bend = this.createHandleShape(0);
+
+ this.initBend(bend);
+ bend.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state);
+ bends.push(bend);
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+
+ // Virtual
+ bends.push(this.createVirtualBend());
+ this.points.push(new mxPoint(0,0));
+
+ // Target
+ bend = this.createHandleShape(2);
+
+ this.initBend(bend);
+ bend.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state);
+ bends.push(bend);
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+
+ return bends;
+ };
+
+/**
+ * Function: createVirtualBend
+ *
+ * Creates a virtual bend that supports double clicking and calls
+ * <mxGraph.flipEdge>.
+ */
+mxElbowEdgeHandler.prototype.createVirtualBend = function()
+{
+ var bend = this.createHandleShape();
+ this.initBend(bend);
+
+ var crs = this.getCursorForBend();
+ bend.node.style.cursor = crs;
+
+ // Double-click changes edge style
+ var dblClick = mxUtils.bind(this, function(evt)
+ {
+ if (!mxEvent.isConsumed(evt) &&
+ this.flipEnabled)
+ {
+ this.graph.flipEdge(this.state.cell, evt);
+ mxEvent.consume(evt);
+ }
+ });
+
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state,
+ null, null, null, dblClick);
+
+ if (!this.graph.isCellBendable(this.state.cell))
+ {
+ bend.node.style.visibility = 'hidden';
+ }
+
+ return bend;
+};
+
+/**
+ * Function: getCursorForBend
+ *
+ * Returns the cursor to be used for the bend.
+ */
+mxElbowEdgeHandler.prototype.getCursorForBend = function()
+{
+ return (this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.TopToBottom ||
+ this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_TOPTOBOTTOM ||
+ ((this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.ElbowConnector ||
+ this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_ELBOW)&&
+ this.state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)) ?
+ 'row-resize' : 'col-resize';
+};
+
+/**
+ * Function: getTooltipForNode
+ *
+ * Returns the tooltip for the given node.
+ */
+mxElbowEdgeHandler.prototype.getTooltipForNode = function(node)
+{
+ var tip = null;
+
+ if (this.bends != null &&
+ this.bends[1] != null &&
+ (node == this.bends[1].node ||
+ node.parentNode == this.bends[1].node))
+ {
+ tip = this.doubleClickOrientationResource;
+ tip = mxResources.get(tip) || tip; // translate
+ }
+
+ return tip;
+};
+
+/**
+ * Function: convertPoint
+ *
+ * Converts the given point in-place from screen to unscaled, untranslated
+ * graph coordinates and applies the grid.
+ *
+ * Parameters:
+ *
+ * point - <mxPoint> to be converted.
+ * gridEnabled - Boolean that specifies if the grid should be applied.
+ */
+mxElbowEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
+{
+ var scale = this.graph.getView().getScale();
+ var tr = this.graph.getView().getTranslate();
+ var origin = this.state.origin;
+
+ if (gridEnabled)
+ {
+ point.x = this.graph.snap(point.x);
+ point.y = this.graph.snap(point.y);
+ }
+
+ point.x = Math.round(point.x / scale - tr.x - origin.x);
+ point.y = Math.round(point.y / scale - tr.y - origin.y);
+};
+
+/**
+ * Function: redrawInnerBends
+ *
+ * Updates and redraws the inner bends.
+ *
+ * Parameters:
+ *
+ * p0 - <mxPoint> that represents the location of the first point.
+ * pe - <mxPoint> that represents the location of the last point.
+ */
+mxElbowEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+ var g = this.graph.getModel().getGeometry(this.state.cell);
+ var pts = g.points;
+
+ var pt = (pts != null) ? pts[0] : null;
+
+ if (pt == null)
+ {
+ pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
+ }
+ else
+ {
+ pt = new mxPoint(this.graph.getView().scale*(pt.x +
+ this.graph.getView().translate.x + this.state.origin.x),
+ this.graph.getView().scale*(pt.y + this.graph.getView().translate.y +
+ this.state.origin.y));
+ }
+
+ // Makes handle slightly bigger if the yellow label handle
+ // exists and intersects this green handle
+ var b = this.bends[1].bounds;
+ var w = b.width;
+ var h = b.height;
+
+ if (this.handleImage == null)
+ {
+ w = mxConstants.HANDLE_SIZE;
+ h = mxConstants.HANDLE_SIZE;
+ }
+
+ var bounds = new mxRectangle(pt.x - w / 2, pt.y - h / 2, w, h);
+
+ if (this.handleImage == null && this.labelShape.node.style.visibility != 'hidden' &&
+ mxUtils.intersects(bounds, this.labelShape.bounds))
+ {
+ w += 3;
+ h += 3;
+ bounds = new mxRectangle(pt.x - w / 2, pt.y - h / 2, w, h);
+ }
+
+ this.bends[1].bounds = bounds;
+ this.bends[1].reconfigure();
+ this.bends[1].redraw();
+};
diff --git a/src/js/handler/mxGraphHandler.js b/src/js/handler/mxGraphHandler.js
new file mode 100644
index 0000000..57e27a1
--- /dev/null
+++ b/src/js/handler/mxGraphHandler.js
@@ -0,0 +1,916 @@
+/**
+ * $Id: mxGraphHandler.js,v 1.129 2012-04-13 12:53:30 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphHandler
+ *
+ * Graph event handler that handles selection. Individual cells are handled
+ * separately using <mxVertexHandler> or one of the edge handlers. These
+ * handlers are created using <mxGraph.createHandler> in
+ * <mxGraphSelectionModel.cellAdded>.
+ *
+ * To avoid the container to scroll a moved cell into view, set
+ * <scrollAfterMove> to false.
+ *
+ * Constructor: mxGraphHandler
+ *
+ * Constructs an event handler that creates handles for the
+ * selection cells.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphHandler(graph)
+{
+ this.graph = graph;
+ this.graph.addMouseListener(this);
+
+ // Repaints the handler after autoscroll
+ this.panHandler = mxUtils.bind(this, function()
+ {
+ this.updatePreviewShape();
+ });
+
+ this.graph.addListener(mxEvent.PAN, this.panHandler);
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphHandler.prototype.graph = null;
+
+/**
+ * Variable: maxCells
+ *
+ * Defines the maximum number of cells to paint subhandles
+ * for. Default is 50 for Firefox and 20 for IE. Set this
+ * to 0 if you want an unlimited number of handles to be
+ * displayed. This is only recommended if the number of
+ * cells in the graph is limited to a small number, eg.
+ * 500.
+ */
+mxGraphHandler.prototype.maxCells = (mxClient.IS_IE) ? 20 : 50;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxGraphHandler.prototype.enabled = true;
+
+/**
+ * Variable: highlightEnabled
+ *
+ * Specifies if drop targets under the mouse should be enabled. Default is
+ * true.
+ */
+mxGraphHandler.prototype.highlightEnabled = true;
+
+/**
+ * Variable: cloneEnabled
+ *
+ * Specifies if cloning by control-drag is enabled. Default is true.
+ */
+mxGraphHandler.prototype.cloneEnabled = true;
+
+/**
+ * Variable: moveEnabled
+ *
+ * Specifies if moving is enabled. Default is true.
+ */
+mxGraphHandler.prototype.moveEnabled = true;
+
+/**
+ * Variable: guidesEnabled
+ *
+ * Specifies if other cells should be used for snapping the right, center or
+ * left side of the current selection. Default is false.
+ */
+mxGraphHandler.prototype.guidesEnabled = false;
+
+/**
+ * Variable: guide
+ *
+ * Holds the <mxGuide> instance that is used for alignment.
+ */
+mxGraphHandler.prototype.guide = null;
+
+/**
+ * Variable: currentDx
+ *
+ * Stores the x-coordinate of the current mouse move.
+ */
+mxGraphHandler.prototype.currentDx = null;
+
+/**
+ * Variable: currentDy
+ *
+ * Stores the y-coordinate of the current mouse move.
+ */
+mxGraphHandler.prototype.currentDy = null;
+
+/**
+ * Variable: updateCursor
+ *
+ * Specifies if a move cursor should be shown if the mouse is ove a movable
+ * cell. Default is true.
+ */
+mxGraphHandler.prototype.updateCursor = true;
+
+/**
+ * Variable: selectEnabled
+ *
+ * Specifies if selecting is enabled. Default is true.
+ */
+mxGraphHandler.prototype.selectEnabled = true;
+
+/**
+ * Variable: removeCellsFromParent
+ *
+ * Specifies if cells may be moved out of their parents. Default is true.
+ */
+mxGraphHandler.prototype.removeCellsFromParent = true;
+
+/**
+ * Variable: connectOnDrop
+ *
+ * Specifies if drop events are interpreted as new connections if no other
+ * drop action is defined. Default is false.
+ */
+mxGraphHandler.prototype.connectOnDrop = false;
+
+/**
+ * Variable: scrollOnMove
+ *
+ * Specifies if the view should be scrolled so that a moved cell is
+ * visible. Default is true.
+ */
+mxGraphHandler.prototype.scrollOnMove = true;
+
+/**
+ * Variable: minimumSize
+ *
+ * Specifies the minimum number of pixels for the width and height of a
+ * selection border. Default is 6.
+ */
+mxGraphHandler.prototype.minimumSize = 6;
+
+/**
+ * Variable: previewColor
+ *
+ * Specifies the color of the preview shape. Default is black.
+ */
+mxGraphHandler.prototype.previewColor = 'black';
+
+/**
+ * Variable: htmlPreview
+ *
+ * Specifies if the graph container should be used for preview. If this is used
+ * then drop target detection relies entirely on <mxGraph.getCellAt> because
+ * the HTML preview does not "let events through". Default is false.
+ */
+mxGraphHandler.prototype.htmlPreview = false;
+
+/**
+ * Variable: shape
+ *
+ * Reference to the <mxShape> that represents the preview.
+ */
+mxGraphHandler.prototype.shape = null;
+
+/**
+ * Variable: scaleGrid
+ *
+ * Specifies if the grid should be scaled. Default is false.
+ */
+mxGraphHandler.prototype.scaleGrid = false;
+
+/**
+ * Variable: crisp
+ *
+ * Specifies if the move preview should be rendered in crisp mode if applicable.
+ * Default is true.
+ */
+mxGraphHandler.prototype.crisp = true;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns <enabled>.
+ */
+mxGraphHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Sets <enabled>.
+ */
+mxGraphHandler.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isCloneEnabled
+ *
+ * Returns <cloneEnabled>.
+ */
+mxGraphHandler.prototype.isCloneEnabled = function()
+{
+ return this.cloneEnabled;
+};
+
+/**
+ * Function: setCloneEnabled
+ *
+ * Sets <cloneEnabled>.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies the new clone enabled state.
+ */
+mxGraphHandler.prototype.setCloneEnabled = function(value)
+{
+ this.cloneEnabled = value;
+};
+
+/**
+ * Function: isMoveEnabled
+ *
+ * Returns <moveEnabled>.
+ */
+mxGraphHandler.prototype.isMoveEnabled = function()
+{
+ return this.moveEnabled;
+};
+
+/**
+ * Function: setMoveEnabled
+ *
+ * Sets <moveEnabled>.
+ */
+mxGraphHandler.prototype.setMoveEnabled = function(value)
+{
+ this.moveEnabled = value;
+};
+
+/**
+ * Function: isSelectEnabled
+ *
+ * Returns <selectEnabled>.
+ */
+mxGraphHandler.prototype.isSelectEnabled = function()
+{
+ return this.selectEnabled;
+};
+
+/**
+ * Function: setSelectEnabled
+ *
+ * Sets <selectEnabled>.
+ */
+mxGraphHandler.prototype.setSelectEnabled = function(value)
+{
+ this.selectEnabled = value;
+};
+
+/**
+ * Function: isRemoveCellsFromParent
+ *
+ * Returns <removeCellsFromParent>.
+ */
+mxGraphHandler.prototype.isRemoveCellsFromParent = function()
+{
+ return this.removeCellsFromParent;
+};
+
+/**
+ * Function: setRemoveCellsFromParent
+ *
+ * Sets <removeCellsFromParent>.
+ */
+mxGraphHandler.prototype.setRemoveCellsFromParent = function(value)
+{
+ this.removeCellsFromParent = value;
+};
+
+/**
+ * Function: getInitialCellForEvent
+ *
+ * Hook to return initial cell for the given event.
+ */
+mxGraphHandler.prototype.getInitialCellForEvent = function(me)
+{
+ return me.getCell();
+};
+
+/**
+ * Function: isDelayedSelection
+ *
+ * Hook to return true for delayed selections.
+ */
+mxGraphHandler.prototype.isDelayedSelection = function(cell)
+{
+ return this.graph.isCellSelected(cell);
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by selecing the given cell and creating a handle for
+ * it. By consuming the event all subsequent events of the gesture are
+ * redirected to this handler.
+ */
+mxGraphHandler.prototype.mouseDown = function(sender, me)
+{
+ if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
+ !this.graph.isForceMarqueeEvent(me.getEvent()) && me.getState() != null)
+ {
+ var cell = this.getInitialCellForEvent(me);
+ this.cell = null;
+ this.delayedSelection = this.isDelayedSelection(cell);
+
+ if (this.isSelectEnabled() && !this.delayedSelection)
+ {
+ this.graph.selectCellForEvent(cell, me.getEvent());
+ }
+
+ if (this.isMoveEnabled())
+ {
+ var model = this.graph.model;
+ var geo = model.getGeometry(cell);
+
+ if (this.graph.isCellMovable(cell) && ((!model.isEdge(cell) || this.graph.getSelectionCount() > 1 ||
+ (geo.points != null && geo.points.length > 0) || model.getTerminal(cell, true) == null ||
+ model.getTerminal(cell, false) == null) || this.graph.allowDanglingEdges ||
+ (this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable())))
+ {
+ this.start(cell, me.getX(), me.getY());
+ }
+
+ this.cellWasClicked = true;
+
+ // Workaround for SELECT element not working in Webkit, this blocks moving
+ // of the cell if the select element is clicked in Safari which is needed
+ // because Safari doesn't seem to route the subsequent mouseUp event via
+ // this handler which leads to an inconsistent state (no reset called).
+ // Same for cellWasClicked which will block clearing the selection when
+ // clicking the background after clicking on the SELECT element in Safari.
+ if ((!mxClient.IS_SF && !mxClient.IS_GC) || me.getSource().nodeName != 'SELECT')
+ {
+ me.consume();
+ }
+ else if (mxClient.IS_SF && me.getSource().nodeName == 'SELECT')
+ {
+ this.cellWasClicked = false;
+ this.first = null;
+ }
+ }
+ }
+};
+
+/**
+ * Function: getGuideStates
+ *
+ * Creates an array of cell states which should be used as guides.
+ */
+mxGraphHandler.prototype.getGuideStates = function()
+{
+ var parent = this.graph.getDefaultParent();
+ var model = this.graph.getModel();
+
+ var filter = mxUtils.bind(this, function(cell)
+ {
+ return this.graph.view.getState(cell) != null &&
+ model.isVertex(cell) &&
+ model.getGeometry(cell) != null &&
+ !model.getGeometry(cell).relative;
+ });
+
+ return this.graph.view.getCellStates(model.filterDescendants(filter, parent));
+};
+
+/**
+ * Function: getCells
+ *
+ * Returns the cells to be modified by this handler. This implementation
+ * returns all selection cells that are movable, or the given initial cell if
+ * the given cell is not selected and movable. This handles the case of moving
+ * unselectable or unselected cells.
+ *
+ * Parameters:
+ *
+ * initialCell - <mxCell> that triggered this handler.
+ */
+mxGraphHandler.prototype.getCells = function(initialCell)
+{
+ if (!this.delayedSelection && this.graph.isCellMovable(initialCell))
+ {
+ return [initialCell];
+ }
+ else
+ {
+ return this.graph.getMovableCells(this.graph.getSelectionCells());
+ }
+};
+
+/**
+ * Function: getPreviewBounds
+ *
+ * Returns the <mxRectangle> used as the preview bounds for
+ * moving the given cells.
+ */
+mxGraphHandler.prototype.getPreviewBounds = function(cells)
+{
+ var bounds = this.graph.getView().getBounds(cells);
+
+ if (bounds != null)
+ {
+ if (bounds.width < this.minimumSize)
+ {
+ var dx = this.minimumSize - bounds.width;
+ bounds.x -= dx / 2;
+ bounds.width = this.minimumSize;
+ }
+
+ if (bounds.height < this.minimumSize)
+ {
+ var dy = this.minimumSize - bounds.height;
+ bounds.y -= dy / 2;
+ bounds.height = this.minimumSize;
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: createPreviewShape
+ *
+ * Creates the shape used to draw the preview for the given bounds.
+ */
+mxGraphHandler.prototype.createPreviewShape = function(bounds)
+{
+ var shape = new mxRectangleShape(bounds, null, this.previewColor);
+ shape.isDashed = true;
+ shape.crisp = this.crisp;
+
+ if (this.htmlPreview)
+ {
+ shape.dialect = mxConstants.DIALECT_STRICTHTML;
+ shape.init(this.graph.container);
+ }
+ else
+ {
+ // Makes sure to use either VML or SVG shapes in order to implement
+ // event-transparency on the background area of the rectangle since
+ // HTML shapes do not let mouseevents through even when transparent
+ shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ shape.init(this.graph.getView().getOverlayPane());
+
+ // Event-transparency
+ if (shape.dialect == mxConstants.DIALECT_SVG)
+ {
+ shape.node.setAttribute('style', 'pointer-events:none;');
+ }
+ else
+ {
+ shape.node.style.background = '';
+ }
+ }
+
+ return shape;
+};
+
+/**
+ * Function: start
+ *
+ * Starts the handling of the mouse gesture.
+ */
+mxGraphHandler.prototype.start = function(cell, x, y)
+{
+ this.cell = cell;
+ this.first = mxUtils.convertPoint(this.graph.container, x, y);
+ this.cells = this.getCells(this.cell);
+ this.bounds = this.getPreviewBounds(this.cells);
+
+ if (this.guidesEnabled)
+ {
+ this.guide = new mxGuide(this.graph, this.getGuideStates());
+ }
+};
+
+/**
+ * Function: useGuidesForEvent
+ *
+ * Returns true if the guides should be used for the given <mxMouseEvent>.
+ * This implementation returns <mxGuide.isEnabledForEvent>.
+ */
+mxGraphHandler.prototype.useGuidesForEvent = function(me)
+{
+ return (this.guide != null) ? this.guide.isEnabledForEvent(me.getEvent()) : true;
+};
+
+
+/**
+ * Function: snap
+ *
+ * Snaps the given vector to the grid and returns the given mxPoint instance.
+ */
+mxGraphHandler.prototype.snap = function(vector)
+{
+ var scale = (this.scaleGrid) ? this.graph.view.scale : 1;
+
+ vector.x = this.graph.snap(vector.x / scale) * scale;
+ vector.y = this.graph.snap(vector.y / scale) * scale;
+
+ return vector;
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by highlighting possible drop targets and updating the
+ * preview.
+ */
+mxGraphHandler.prototype.mouseMove = function(sender, me)
+{
+ var graph = this.graph;
+
+ if (!me.isConsumed() && graph.isMouseDown && this.cell != null &&
+ this.first != null && this.bounds != null)
+ {
+ var point = mxUtils.convertPoint(graph.container, me.getX(), me.getY());
+ var dx = point.x - this.first.x;
+ var dy = point.y - this.first.y;
+ var tol = graph.tolerance;
+
+ if (this.shape!= null || Math.abs(dx) > tol || Math.abs(dy) > tol)
+ {
+ // Highlight is used for highlighting drop targets
+ if (this.highlight == null)
+ {
+ this.highlight = new mxCellHighlight(this.graph,
+ mxConstants.DROP_TARGET_COLOR, 3);
+ }
+
+ if (this.shape == null)
+ {
+ this.shape = this.createPreviewShape(this.bounds);
+ }
+
+ var gridEnabled = graph.isGridEnabledEvent(me.getEvent());
+ var hideGuide = true;
+
+ if (this.guide != null && this.useGuidesForEvent(me))
+ {
+ var delta = this.guide.move(this.bounds, new mxPoint(dx, dy), gridEnabled);
+ hideGuide = false;
+ dx = delta.x;
+ dy = delta.y;
+ }
+ else if (gridEnabled)
+ {
+ var trx = graph.getView().translate;
+ var scale = graph.getView().scale;
+
+ var tx = this.bounds.x - (graph.snap(this.bounds.x / scale - trx.x) + trx.x) * scale;
+ var ty = this.bounds.y - (graph.snap(this.bounds.y / scale - trx.y) + trx.y) * scale;
+ var v = this.snap(new mxPoint(dx, dy));
+
+ dx = v.x - tx;
+ dy = v.y - ty;
+ }
+
+ if (this.guide != null && hideGuide)
+ {
+ this.guide.hide();
+ }
+
+ // Constrained movement if shift key is pressed
+ if (graph.isConstrainedEvent(me.getEvent()))
+ {
+ if (Math.abs(dx) > Math.abs(dy))
+ {
+ dy = 0;
+ }
+ else
+ {
+ dx = 0;
+ }
+ }
+
+ this.currentDx = dx;
+ this.currentDy = dy;
+ this.updatePreviewShape();
+
+ var target = null;
+ var cell = me.getCell();
+
+ if (graph.isDropEnabled() && this.highlightEnabled)
+ {
+ // Contains a call to getCellAt to find the cell under the mouse
+ target = graph.getDropTarget(this.cells, me.getEvent(), cell);
+ }
+
+ // Checks if parent is dropped into child
+ var parent = target;
+ var model = graph.getModel();
+
+ while (parent != null && parent != this.cells[0])
+ {
+ parent = model.getParent(parent);
+ }
+
+ var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
+ var state = graph.getView().getState(target);
+ var highlight = false;
+
+ if (state != null && parent == null && (model.getParent(this.cell) != target || clone))
+ {
+ if (this.target != target)
+ {
+ this.target = target;
+ this.setHighlightColor(mxConstants.DROP_TARGET_COLOR);
+ }
+
+ highlight = true;
+ }
+ else
+ {
+ this.target = null;
+
+ if (this.connectOnDrop && cell != null && this.cells.length == 1 &&
+ graph.getModel().isVertex(cell) && graph.isCellConnectable(cell))
+ {
+ state = graph.getView().getState(cell);
+
+ if (state != null)
+ {
+ var error = graph.getEdgeValidationError(null, this.cell, cell);
+ var color = (error == null) ?
+ mxConstants.VALID_COLOR :
+ mxConstants.INVALID_CONNECT_TARGET_COLOR;
+ this.setHighlightColor(color);
+ highlight = true;
+ }
+ }
+ }
+
+ if (state != null && highlight)
+ {
+ this.highlight.highlight(state);
+ }
+ else
+ {
+ this.highlight.hide();
+ }
+ }
+
+ me.consume();
+
+ // Cancels the bubbling of events to the container so
+ // that the droptarget is not reset due to an mouseMove
+ // fired on the container with no associated state.
+ mxEvent.consume(me.getEvent());
+ }
+ else if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor &&
+ !me.isConsumed() && me.getState() != null && !graph.isMouseDown)
+ {
+ var cursor = graph.getCursorForCell(me.getCell());
+
+ if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell()))
+ {
+ if (graph.getModel().isEdge(me.getCell()))
+ {
+ cursor = mxConstants.CURSOR_MOVABLE_EDGE;
+ }
+ else
+ {
+ cursor = mxConstants.CURSOR_MOVABLE_VERTEX;
+ }
+ }
+
+ me.getState().setCursor(cursor);
+ me.consume();
+ }
+};
+
+/**
+ * Function: updatePreviewShape
+ *
+ * Updates the bounds of the preview shape.
+ */
+mxGraphHandler.prototype.updatePreviewShape = function()
+{
+ if (this.shape != null)
+ {
+ this.shape.bounds = new mxRectangle(this.bounds.x + this.currentDx - this.graph.panDx,
+ this.bounds.y + this.currentDy - this.graph.panDy, this.bounds.width, this.bounds.height);
+ this.shape.redraw();
+ }
+};
+
+/**
+ * Function: setHighlightColor
+ *
+ * Sets the color of the rectangle used to highlight drop targets.
+ *
+ * Parameters:
+ *
+ * color - String that represents the new highlight color.
+ */
+mxGraphHandler.prototype.setHighlightColor = function(color)
+{
+ if (this.highlight != null)
+ {
+ this.highlight.setHighlightColor(color);
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by applying the changes to the selection cells.
+ */
+mxGraphHandler.prototype.mouseUp = function(sender, me)
+{
+ if (!me.isConsumed())
+ {
+ var graph = this.graph;
+
+ if (this.cell != null && this.first != null && this.shape != null &&
+ this.currentDx != null && this.currentDy != null)
+ {
+ var scale = graph.getView().scale;
+ var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
+ var dx = this.currentDx / scale;
+ var dy = this.currentDy / scale;
+
+ var cell = me.getCell();
+
+ if (this.connectOnDrop && this.target == null && cell != null && graph.getModel().isVertex(cell) &&
+ graph.isCellConnectable(cell) && graph.isEdgeValid(null, this.cell, cell))
+ {
+ graph.connectionHandler.connect(this.cell, cell, me.getEvent());
+ }
+ else
+ {
+ var target = this.target;
+
+ if (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent()))
+ {
+ graph.splitEdge(target, this.cells, null, dx, dy);
+ }
+ else
+ {
+ this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent());
+ }
+ }
+ }
+ else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null)
+ {
+ this.selectDelayed(me);
+ }
+ }
+
+ // Consumes the event if a cell was initially clicked
+ if (this.cellWasClicked)
+ {
+ me.consume();
+ }
+
+ this.reset();
+};
+
+/**
+ * Function: selectDelayed
+ *
+ * Implements the delayed selection for the given mouse event.
+ */
+mxGraphHandler.prototype.selectDelayed = function(me)
+{
+ this.graph.selectCellForEvent(this.cell, me.getEvent());
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this handler.
+ */
+mxGraphHandler.prototype.reset = function()
+{
+ this.destroyShapes();
+ this.cellWasClicked = false;
+ this.delayedSelection = false;
+ this.currentDx = null;
+ this.currentDy = null;
+ this.guides = null;
+ this.first = null;
+ this.cell = null;
+ this.target = null;
+};
+
+/**
+ * Function: shouldRemoveCellsFromParent
+ *
+ * Returns true if the given cells should be removed from the parent for the specified
+ * mousereleased event.
+ */
+mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt)
+{
+ if (this.graph.getModel().isVertex(parent))
+ {
+ var pState = this.graph.getView().getState(parent);
+ var pt = mxUtils.convertPoint(this.graph.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ return pState != null && !mxUtils.contains(pState, pt.x, pt.y);
+ }
+
+ return false;
+};
+
+/**
+ * Function: moveCells
+ *
+ * Moves the given cells by the specified amount.
+ */
+mxGraphHandler.prototype.moveCells = function(cells, dx, dy, clone, target, evt)
+{
+ if (clone)
+ {
+ cells = this.graph.getCloneableCells(cells);
+ }
+
+ // Removes cells from parent
+ if (target == null && this.isRemoveCellsFromParent() &&
+ this.shouldRemoveCellsFromParent(this.graph.getModel().getParent(this.cell), cells, evt))
+ {
+ target = this.graph.getDefaultParent();
+ }
+
+ // Passes all selected cells in order to correctly clone or move into
+ // the target cell. The method checks for each cell if its movable.
+ cells = this.graph.moveCells(cells, dx - this.graph.panDx / this.graph.view.scale,
+ dy - this.graph.panDy / this.graph.view.scale, clone, target, evt);
+
+ if (this.isSelectEnabled() && this.scrollOnMove)
+ {
+ this.graph.scrollCellToVisible(cells[0]);
+ }
+
+ // Selects the new cells if cells have been cloned
+ if (clone)
+ {
+ this.graph.setSelectionCells(cells);
+ }
+};
+
+/**
+ * Function: destroyShapes
+ *
+ * Destroy the preview and highlight shapes.
+ */
+mxGraphHandler.prototype.destroyShapes = function()
+{
+ // Destroys the preview dashed rectangle
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+
+ if (this.guide != null)
+ {
+ this.guide.destroy();
+ this.guide = null;
+ }
+
+ // Destroys the drop target highlight
+ if (this.highlight != null)
+ {
+ this.highlight.destroy();
+ this.highlight = null;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxGraphHandler.prototype.destroy = function()
+{
+ this.graph.removeMouseListener(this);
+ this.graph.removeListener(this.panHandler);
+ this.destroyShapes();
+};
diff --git a/src/js/handler/mxKeyHandler.js b/src/js/handler/mxKeyHandler.js
new file mode 100644
index 0000000..cc07e51
--- /dev/null
+++ b/src/js/handler/mxKeyHandler.js
@@ -0,0 +1,402 @@
+/**
+ * $Id: mxKeyHandler.js,v 1.48 2012-03-30 08:30:41 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxKeyHandler
+ *
+ * Event handler that listens to keystroke events. This is not a singleton,
+ * however, it is normally only required once if the target is the document
+ * element (default).
+ *
+ * This handler installs a key event listener in the topmost DOM node and
+ * processes all events that originate from descandants of <mxGraph.container>
+ * or from the topmost DOM node. The latter means that all unhandled keystrokes
+ * are handled by this object regardless of the focused state of the <graph>.
+ *
+ * Example:
+ *
+ * The following example creates a key handler that listens to the delete key
+ * (46) and deletes the selection cells if the graph is enabled.
+ *
+ * (code)
+ * var keyHandler = new mxKeyHandler(graph);
+ * keyHandler.bindKey(46, function(evt)
+ * {
+ * if (graph.isEnabled())
+ * {
+ * graph.removeCells();
+ * }
+ * });
+ * (end)
+ *
+ * Keycodes:
+ *
+ * See http://tinyurl.com/yp8jgl or http://tinyurl.com/229yqw for a list of
+ * keycodes or install a key event listener into the document element and print
+ * the key codes of the respective events to the console.
+ *
+ * To support the Command key and the Control key on the Mac, the following
+ * code can be used.
+ *
+ * (code)
+ * keyHandler.getFunction = function(evt)
+ * {
+ * if (evt != null)
+ * {
+ * return (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode];
+ * }
+ *
+ * return null;
+ * };
+ * (end)
+ *
+ * Constructor: mxKeyHandler
+ *
+ * Constructs an event handler that executes functions bound to specific
+ * keystrokes.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the associated <mxGraph>.
+ * target - Optional reference to the event target. If null, the document
+ * element is used as the event target, that is, the object where the key
+ * event listener is installed.
+ */
+function mxKeyHandler(graph, target)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.target = target || document.documentElement;
+
+ // Creates the arrays to map from keycodes to functions
+ this.normalKeys = [];
+ this.shiftKeys = [];
+ this.controlKeys = [];
+ this.controlShiftKeys = [];
+
+ // Installs the keystroke listener in the target
+ mxEvent.addListener(this.target, "keydown",
+ mxUtils.bind(this, function(evt)
+ {
+ this.keyDown(evt);
+ })
+ );
+
+ // Automatically deallocates memory in IE
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload',
+ mxUtils.bind(this, function()
+ {
+ this.destroy();
+ })
+ );
+ }
+ }
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the <mxGraph> associated with this handler.
+ */
+mxKeyHandler.prototype.graph = null;
+
+/**
+ * Variable: target
+ *
+ * Reference to the target DOM, that is, the DOM node where the key event
+ * listeners are installed.
+ */
+mxKeyHandler.prototype.target = null;
+
+/**
+ * Variable: normalKeys
+ *
+ * Maps from keycodes to functions for non-pressed control keys.
+ */
+mxKeyHandler.prototype.normalKeys = null;
+
+/**
+ * Variable: shiftKeys
+ *
+ * Maps from keycodes to functions for pressed shift keys.
+ */
+mxKeyHandler.prototype.shiftKeys = null;
+
+/**
+ * Variable: controlKeys
+ *
+ * Maps from keycodes to functions for pressed control keys.
+ */
+mxKeyHandler.prototype.controlKeys = null;
+
+/**
+ * Variable: controlShiftKeys
+ *
+ * Maps from keycodes to functions for pressed control and shift keys.
+ */
+mxKeyHandler.prototype.controlShiftKeys = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxKeyHandler.prototype.enabled = true;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation returns
+ * <enabled>.
+ */
+mxKeyHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling by updating <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxKeyHandler.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: bindKey
+ *
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control key is not pressed.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindKey = function(code, funct)
+{
+ this.normalKeys[code] = funct;
+};
+
+/**
+ * Function: bindShiftKey
+ *
+ * Binds the specified keycode to the given function. This binding is used
+ * if the shift key is pressed.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindShiftKey = function(code, funct)
+{
+ this.shiftKeys[code] = funct;
+};
+
+/**
+ * Function: bindControlKey
+ *
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control key is pressed.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindControlKey = function(code, funct)
+{
+ this.controlKeys[code] = funct;
+};
+
+/**
+ * Function: bindControlShiftKey
+ *
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control and shift key are pressed.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindControlShiftKey = function(code, funct)
+{
+ this.controlShiftKeys[code] = funct;
+};
+
+/**
+ * Function: isControlDown
+ *
+ * Returns true if the control key is pressed. This uses <mxEvent.isControlDown>.
+ *
+ * Parameters:
+ *
+ * evt - Key event whose control key pressed state should be returned.
+ */
+mxKeyHandler.prototype.isControlDown = function(evt)
+{
+ return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: getFunction
+ *
+ * Returns the function associated with the given key event or null if no
+ * function is associated with the given event.
+ *
+ * Parameters:
+ *
+ * evt - Key event whose associated function should be returned.
+ */
+mxKeyHandler.prototype.getFunction = function(evt)
+{
+ if (evt != null)
+ {
+ if (this.isControlDown(evt))
+ {
+ if (mxEvent.isShiftDown(evt))
+ {
+ return this.controlShiftKeys[evt.keyCode];
+ }
+ else
+ {
+ return this.controlKeys[evt.keyCode];
+ }
+ }
+ else
+ {
+ if (mxEvent.isShiftDown(evt))
+ {
+ return this.shiftKeys[evt.keyCode];
+ }
+ else
+ {
+ return this.normalKeys[evt.keyCode];
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: isGraphEvent
+ *
+ * Returns true if the event should be processed by this handler, that is,
+ * if the event source is either the target, one of its direct children, a
+ * descendant of the <mxGraph.container>, or the <mxGraph.cellEditor> of the
+ * <graph>.
+ *
+ * Parameters:
+ *
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isGraphEvent = function(evt)
+{
+ var source = mxEvent.getSource(evt);
+
+ // Accepts events from the target object or
+ // in-place editing inside graph
+ if ((source == this.target || source.parentNode == this.target) ||
+ (this.graph.cellEditor != null && source == this.graph.cellEditor.textarea))
+ {
+ return true;
+ }
+
+ // Accepts events from inside the container
+ var elt = source;
+
+ while (elt != null)
+ {
+ if (elt == this.graph.container)
+ {
+ return true;
+ }
+
+ elt = elt.parentNode;
+ }
+
+ return false;
+};
+
+/**
+ * Function: keyDown
+ *
+ * Handles the event by invoking the function bound to the respective
+ * keystroke if <mxGraph.isEnabled>, <isEnabled> and <isGraphEvent> all
+ * return true for the given event and <mxGraph.isEditing> returns false.
+ * If the graph is editing only the <enter> and <escape> cases are handled
+ * by calling the respective hooks.
+ *
+ * Parameters:
+ *
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.keyDown = function(evt)
+{
+ if (this.graph.isEnabled() && !mxEvent.isConsumed(evt) &&
+ this.isGraphEvent(evt) && this.isEnabled())
+ {
+ // Cancels the editing if escape is pressed
+ if (evt.keyCode == 27 /* Escape */)
+ {
+ this.escape(evt);
+ }
+
+ // Invokes the function for the keystroke
+ else if (!this.graph.isEditing())
+ {
+ var boundFunction = this.getFunction(evt);
+
+ if (boundFunction != null)
+ {
+ boundFunction(evt);
+ mxEvent.consume(evt);
+ }
+ }
+ }
+};
+
+/**
+ * Function: escape
+ *
+ * Hook to process ESCAPE keystrokes. This implementation invokes
+ * <mxGraph.stopEditing> to cancel the current editing, connecting
+ * and/or other ongoing modifications.
+ *
+ * Parameters:
+ *
+ * evt - Key event that represents the keystroke. Possible keycode in this
+ * case is 27 (ESCAPE).
+ */
+mxKeyHandler.prototype.escape = function(evt)
+{
+ if (this.graph.isEscapeEnabled())
+ {
+ this.graph.escape(evt);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its references into the DOM. This does
+ * normally not need to be called, it is called automatically when the
+ * window unloads (in IE).
+ */
+mxKeyHandler.prototype.destroy = function()
+{
+ this.target = null;
+};
diff --git a/src/js/handler/mxPanningHandler.js b/src/js/handler/mxPanningHandler.js
new file mode 100644
index 0000000..b388144
--- /dev/null
+++ b/src/js/handler/mxPanningHandler.js
@@ -0,0 +1,390 @@
+/**
+ * $Id: mxPanningHandler.js,v 1.79 2012-07-17 14:37:41 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPanningHandler
+ *
+ * Event handler that pans and creates popupmenus. To use the left
+ * mousebutton for panning without interfering with cell moving and
+ * resizing, use <isUseLeftButton> and <isIgnoreCell>. For grid size
+ * steps while panning, use <useGrid>. This handler is built-into
+ * <mxGraph.panningHandler> and enabled using <mxGraph.setPanning>.
+ *
+ * Constructor: mxPanningHandler
+ *
+ * Constructs an event handler that creates a <mxPopupMenu>
+ * and pans the graph.
+ *
+ * Event: mxEvent.PAN_START
+ *
+ * Fires when the panning handler changes its <active> state to true. The
+ * <code>event</code> property contains the corresponding <mxMouseEvent>.
+ *
+ * Event: mxEvent.PAN
+ *
+ * Fires while handle is processing events. The <code>event</code> property contains
+ * the corresponding <mxMouseEvent>.
+ *
+ * Event: mxEvent.PAN_END
+ *
+ * Fires when the panning handler changes its <active> state to false. The
+ * <code>event</code> property contains the corresponding <mxMouseEvent>.
+ */
+function mxPanningHandler(graph, factoryMethod)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.factoryMethod = factoryMethod;
+ this.graph.addMouseListener(this);
+ this.init();
+ }
+};
+
+/**
+ * Extends mxPopupMenu.
+ */
+mxPanningHandler.prototype = new mxPopupMenu();
+mxPanningHandler.prototype.constructor = mxPanningHandler;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxPanningHandler.prototype.graph = null;
+
+/**
+ * Variable: usePopupTrigger
+ *
+ * Specifies if the <isPopupTrigger> should also be used for panning. To
+ * avoid conflicts, the panning is only activated if the mouse was moved
+ * more than <mxGraph.tolerance>, otherwise, a single click is assumed
+ * and the popupmenu is displayed. Default is true.
+ */
+mxPanningHandler.prototype.usePopupTrigger = true;
+
+/**
+ * Variable: useLeftButtonForPanning
+ *
+ * Specifies if panning should be active for the left mouse button.
+ * Setting this to true may conflict with <mxRubberband>. Default is false.
+ */
+mxPanningHandler.prototype.useLeftButtonForPanning = false;
+
+/**
+ * Variable: selectOnPopup
+ *
+ * Specifies if cells should be selected if a popupmenu is displayed for
+ * them. Default is true.
+ */
+mxPanningHandler.prototype.selectOnPopup = true;
+
+/**
+ * Variable: clearSelectionOnBackground
+ *
+ * Specifies if cells should be deselected if a popupmenu is displayed for
+ * the diagram background. Default is true.
+ */
+mxPanningHandler.prototype.clearSelectionOnBackground = true;
+
+/**
+ * Variable: ignoreCell
+ *
+ * Specifies if panning should be active even if there is a cell under the
+ * mousepointer. Default is false.
+ */
+mxPanningHandler.prototype.ignoreCell = false;
+
+/**
+ * Variable: previewEnabled
+ *
+ * Specifies if the panning should be previewed. Default is true.
+ */
+mxPanningHandler.prototype.previewEnabled = true;
+
+/**
+ * Variable: useGrid
+ *
+ * Specifies if the panning steps should be aligned to the grid size.
+ * Default is false.
+ */
+mxPanningHandler.prototype.useGrid = false;
+
+/**
+ * Variable: panningEnabled
+ *
+ * Specifies if panning should be enabled. Default is true.
+ */
+mxPanningHandler.prototype.panningEnabled = true;
+
+/**
+ * Function: isPanningEnabled
+ *
+ * Returns <panningEnabled>.
+ */
+mxPanningHandler.prototype.isPanningEnabled = function()
+{
+ return this.panningEnabled;
+};
+
+/**
+ * Function: setPanningEnabled
+ *
+ * Sets <panningEnabled>.
+ */
+mxPanningHandler.prototype.setPanningEnabled = function(value)
+{
+ this.panningEnabled = value;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPanningHandler.prototype.init = function()
+{
+ // Supercall
+ mxPopupMenu.prototype.init.apply(this);
+
+ // Hides the tooltip if the mouse is over
+ // the context menu
+ mxEvent.addListener(this.div, (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove',
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.tooltipHandler.hide();
+ })
+ );
+};
+
+/**
+ * Function: isPanningTrigger
+ *
+ * Returns true if the given event is a panning trigger for the optional
+ * given cell. This returns true if control-shift is pressed or if
+ * <usePopupTrigger> is true and the event is a popup trigger.
+ */
+mxPanningHandler.prototype.isPanningTrigger = function(me)
+{
+ var evt = me.getEvent();
+
+ return (this.useLeftButtonForPanning && (this.ignoreCell || me.getState() == null) &&
+ mxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) &&
+ mxEvent.isShiftDown(evt)) || (this.usePopupTrigger &&
+ mxEvent.isPopupTrigger(evt));
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by initiating the panning. By consuming the event all
+ * subsequent events of the gesture are redirected to this handler.
+ */
+mxPanningHandler.prototype.mouseDown = function(sender, me)
+{
+ if (!me.isConsumed() && this.isEnabled())
+ {
+ // Hides the popupmenu if is is being displayed
+ this.hideMenu();
+
+ this.dx0 = -this.graph.container.scrollLeft;
+ this.dy0 = -this.graph.container.scrollTop;
+
+ // Checks the event triggers to panning and popupmenu
+ this.popupTrigger = this.isPopupTrigger(me);
+ this.panningTrigger = this.isPanningEnabled() &&
+ this.isPanningTrigger(me);
+
+ // Stores the location of the trigger event
+ this.startX = me.getX();
+ this.startY = me.getY();
+
+ // Displays popup menu on Mac after the mouse was released
+ if (this.panningTrigger)
+ {
+ this.consumePanningTrigger(me);
+ }
+ }
+};
+
+/**
+ * Function: consumePanningTrigger
+ *
+ * Consumes the given <mxMouseEvent> if it was a panning trigger in
+ * <mouseDown>. The default is to invoke <mxMouseEvent.consume>. Note that this
+ * will block any further event processing. If you haven't disabled built-in
+ * context menus and require immediate selection of the cell on mouseDown in
+ * Safari and/or on the Mac, then use the following code:
+ *
+ * (code)
+ * mxPanningHandler.prototype.consumePanningTrigger = function(me)
+ * {
+ * if (me.evt.preventDefault)
+ * {
+ * me.evt.preventDefault();
+ * }
+ *
+ * // Stops event processing in IE
+ * me.evt.returnValue = false;
+ *
+ * // Sets local consumed state
+ * if (!mxClient.IS_SF && !mxClient.IS_MAC)
+ * {
+ * me.consumed = true;
+ * }
+ * };
+ * (end)
+ */
+mxPanningHandler.prototype.consumePanningTrigger = function(me)
+{
+ me.consume();
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating the panning on the graph.
+ */
+mxPanningHandler.prototype.mouseMove = function(sender, me)
+{
+ var dx = me.getX() - this.startX;
+ var dy = me.getY() - this.startY;
+
+ if (this.active)
+ {
+ if (this.previewEnabled)
+ {
+ // Applies the grid to the panning steps
+ if (this.useGrid)
+ {
+ dx = this.graph.snap(dx);
+ dy = this.graph.snap(dy);
+ }
+
+ this.graph.panGraph(dx + this.dx0, dy + this.dy0);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me));
+ me.consume();
+ }
+ else if (this.panningTrigger)
+ {
+ var tmp = this.active;
+
+ // Panning is activated only if the mouse is moved
+ // beyond the graph tolerance
+ this.active = Math.abs(dx) > this.graph.tolerance ||
+ Math.abs(dy) > this.graph.tolerance;
+
+ if (!tmp && this.active)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
+ }
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by setting the translation on the view or showing the
+ * popupmenu.
+ */
+mxPanningHandler.prototype.mouseUp = function(sender, me)
+{
+ // Shows popup menu if mouse was not moved
+ var dx = Math.abs(me.getX() - this.startX);
+ var dy = Math.abs(me.getY() - this.startY);
+
+ if (this.active)
+ {
+ if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container))
+ {
+ dx = me.getX() - this.startX;
+ dy = me.getY() - this.startY;
+
+ // Applies the grid to the panning steps
+ if (this.useGrid)
+ {
+ dx = this.graph.snap(dx);
+ dy = this.graph.snap(dy);
+ }
+
+ var scale = this.graph.getView().scale;
+ var t = this.graph.getView().translate;
+
+ this.graph.panGraph(0, 0);
+ this.panGraph(t.x + dx / scale, t.y + dy / scale);
+ }
+
+ this.active = false;
+ this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me));
+ me.consume();
+ }
+ else if (this.popupTrigger)
+ {
+ if (dx < this.graph.tolerance && dy < this.graph.tolerance)
+ {
+ var cell = this.getCellForPopupEvent(me);
+
+ // Selects the cell for which the context menu is being displayed
+ if (this.graph.isEnabled() && this.selectOnPopup &&
+ cell != null && !this.graph.isCellSelected(cell))
+ {
+ this.graph.setSelectionCell(cell);
+ }
+ else if (this.clearSelectionOnBackground && cell == null)
+ {
+ this.graph.clearSelection();
+ }
+
+ // Hides the tooltip if there is one
+ this.graph.tooltipHandler.hide();
+ var origin = mxUtils.getScrollOrigin();
+ var point = new mxPoint(me.getX() + origin.x,
+ me.getY() + origin.y);
+
+ // Menu is shifted by 1 pixel so that the mouse up event
+ // is routed via the underlying shape instead of the DIV
+ this.popup(point.x + 1, point.y + 1, cell, me.getEvent());
+ me.consume();
+ }
+ }
+
+ this.panningTrigger = false;
+ this.popupTrigger = false;
+};
+
+/**
+ * Function: getCellForPopupEvent
+ *
+ * Hook to return the cell for the mouse up popup trigger handling.
+ */
+mxPanningHandler.prototype.getCellForPopupEvent = function(me)
+{
+ return me.getCell();
+};
+
+/**
+ * Function: panGraph
+ *
+ * Pans <graph> by the given amount.
+ */
+mxPanningHandler.prototype.panGraph = function(dx, dy)
+{
+ this.graph.getView().setTranslate(dx, dy);
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPanningHandler.prototype.destroy = function()
+{
+ this.graph.removeMouseListener(this);
+
+ // Supercall
+ mxPopupMenu.prototype.destroy.apply(this);
+};
diff --git a/src/js/handler/mxRubberband.js b/src/js/handler/mxRubberband.js
new file mode 100644
index 0000000..f9e7187
--- /dev/null
+++ b/src/js/handler/mxRubberband.js
@@ -0,0 +1,348 @@
+/**
+ * $Id: mxRubberband.js,v 1.48 2012-04-13 12:53:30 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxRubberband
+ *
+ * Event handler that selects rectangular regions. This is not built-into
+ * <mxGraph>. To enable rubberband selection in a graph, use the following code.
+ *
+ * Example:
+ *
+ * (code)
+ * var rubberband = new mxRubberband(graph);
+ * (end)
+ *
+ * Constructor: mxRubberband
+ *
+ * Constructs an event handler that selects rectangular regions in the graph
+ * using rubberband selection.
+ */
+function mxRubberband(graph)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.graph.addMouseListener(this);
+
+ // Repaints the marquee after autoscroll
+ this.panHandler = mxUtils.bind(this, function()
+ {
+ this.repaint();
+ });
+
+ this.graph.addListener(mxEvent.PAN, this.panHandler);
+
+ // Automatic deallocation of memory
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload',
+ mxUtils.bind(this, function()
+ {
+ this.destroy();
+ })
+ );
+ }
+ }
+};
+
+/**
+ * Variable: defaultOpacity
+ *
+ * Specifies the default opacity to be used for the rubberband div. Default
+ * is 20.
+ */
+mxRubberband.prototype.defaultOpacity = 20;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxRubberband.prototype.enabled = true;
+
+/**
+ * Variable: div
+ *
+ * Holds the DIV element which is currently visible.
+ */
+mxRubberband.prototype.div = null;
+
+/**
+ * Variable: sharedDiv
+ *
+ * Holds the DIV element which is used to display the rubberband.
+ */
+mxRubberband.prototype.sharedDiv = null;
+
+/**
+ * Variable: currentX
+ *
+ * Holds the value of the x argument in the last call to <update>.
+ */
+mxRubberband.prototype.currentX = 0;
+
+/**
+ * Variable: currentY
+ *
+ * Holds the value of the y argument in the last call to <update>.
+ */
+mxRubberband.prototype.currentY = 0;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation returns
+ * <enabled>.
+ */
+mxRubberband.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation updates
+ * <enabled>.
+ */
+mxRubberband.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by initiating a rubberband selection. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxRubberband.prototype.mouseDown = function(sender, me)
+{
+ if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
+ (this.graph.isForceMarqueeEvent(me.getEvent()) || me.getState() == null))
+ {
+ var offset = mxUtils.getOffset(this.graph.container);
+ var origin = mxUtils.getScrollOrigin(this.graph.container);
+ origin.x -= offset.x;
+ origin.y -= offset.y;
+ this.start(me.getX() + origin.x, me.getY() + origin.y);
+
+ // Workaround for rubberband stopping if the mouse leaves the
+ // graph container in Firefox.
+ if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC)
+ {
+ var container = this.graph.container;
+
+ function createMouseEvent(evt)
+ {
+ var me = new mxMouseEvent(evt);
+ var pt = mxUtils.convertPoint(container, me.getX(), me.getY());
+
+ me.graphX = pt.x;
+ me.graphY = pt.y;
+
+ return me;
+ };
+
+ this.dragHandler = mxUtils.bind(this, function(evt)
+ {
+ this.mouseMove(this.graph, createMouseEvent(evt));
+ });
+
+ this.dropHandler = mxUtils.bind(this, function(evt)
+ {
+ this.mouseUp(this.graph, createMouseEvent(evt));
+ });
+
+ mxEvent.addListener(document, 'mousemove', this.dragHandler);
+ mxEvent.addListener(document, 'mouseup', this.dropHandler);
+ }
+
+ // Does not prevent the default for this event so that the
+ // event processing chain is still executed even if we start
+ // rubberbanding. This is required eg. in ExtJs to hide the
+ // current context menu. In mouseMove we'll make sure we're
+ // not selecting anything while we're rubberbanding.
+ me.consume(false);
+ }
+};
+
+/**
+ * Function: start
+ *
+ * Sets the start point for the rubberband selection.
+ */
+mxRubberband.prototype.start = function(x, y)
+{
+ this.first = new mxPoint(x, y);
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating therubberband selection.
+ */
+mxRubberband.prototype.mouseMove = function(sender, me)
+{
+ if (!me.isConsumed() && this.first != null)
+ {
+ var origin = mxUtils.getScrollOrigin(this.graph.container);
+ var offset = mxUtils.getOffset(this.graph.container);
+ origin.x -= offset.x;
+ origin.y -= offset.y;
+ var x = me.getX() + origin.x;
+ var y = me.getY() + origin.y;
+ var dx = this.first.x - x;
+ var dy = this.first.y - y;
+ var tol = this.graph.tolerance;
+
+ if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol)
+ {
+ if (this.div == null)
+ {
+ this.div = this.createShape();
+ }
+
+ // Clears selection while rubberbanding. This is required because
+ // the event is not consumed in mouseDown.
+ mxUtils.clearSelection();
+
+ this.update(x, y);
+ me.consume();
+ }
+ }
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates the rubberband selection shape.
+ */
+mxRubberband.prototype.createShape = function()
+{
+ if (this.sharedDiv == null)
+ {
+ this.sharedDiv = document.createElement('div');
+ this.sharedDiv.className = 'mxRubberband';
+ mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity);
+ }
+
+ this.graph.container.appendChild(this.sharedDiv);
+
+ return this.sharedDiv;
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by selecting the region of the rubberband using
+ * <mxGraph.selectRegion>.
+ */
+mxRubberband.prototype.mouseUp = function(sender, me)
+{
+ var execute = this.div != null;
+ this.reset();
+
+ if (execute)
+ {
+ var rect = new mxRectangle(this.x, this.y, this.width, this.height);
+ this.graph.selectRegion(rect, me.getEvent());
+ me.consume();
+ }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of the rubberband selection.
+ */
+mxRubberband.prototype.reset = function()
+{
+ if (this.div != null)
+ {
+ this.div.parentNode.removeChild(this.div);
+ }
+
+ if (this.dragHandler != null)
+ {
+ mxEvent.removeListener(document, 'mousemove', this.dragHandler);
+ this.dragHandler = null;
+ }
+
+ if (this.dropHandler != null)
+ {
+ mxEvent.removeListener(document, 'mouseup', this.dropHandler);
+ this.dropHandler = null;
+ }
+
+ this.currentX = 0;
+ this.currentY = 0;
+ this.first = null;
+ this.div = null;
+};
+
+/**
+ * Function: update
+ *
+ * Sets <currentX> and <currentY> and calls <repaint>.
+ */
+mxRubberband.prototype.update = function(x, y)
+{
+ this.currentX = x;
+ this.currentY = y;
+
+ this.repaint();
+};
+
+/**
+ * Function: repaint
+ *
+ * Computes the bounding box and updates the style of the <div>.
+ */
+mxRubberband.prototype.repaint = function()
+{
+ if (this.div != null)
+ {
+ var x = this.currentX - this.graph.panDx;
+ var y = this.currentY - this.graph.panDy;
+
+ this.x = Math.min(this.first.x, x);
+ this.y = Math.min(this.first.y, y);
+ this.width = Math.max(this.first.x, x) - this.x;
+ this.height = Math.max(this.first.y, y) - this.y;
+
+ var dx = (mxClient.IS_VML) ? this.graph.panDx : 0;
+ var dy = (mxClient.IS_VML) ? this.graph.panDy : 0;
+
+ this.div.style.left = (this.x + dx) + 'px';
+ this.div.style.top = (this.y + dy) + 'px';
+ this.div.style.width = Math.max(1, this.width) + 'px';
+ this.div.style.height = Math.max(1, this.height) + 'px';
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes. This does
+ * normally not need to be called, it is called automatically when the
+ * window unloads.
+ */
+mxRubberband.prototype.destroy = function()
+{
+ if (!this.destroyed)
+ {
+ this.destroyed = true;
+ this.graph.removeMouseListener(this);
+ this.graph.removeListener(this.panHandler);
+ this.reset();
+
+ if (this.sharedDiv != null)
+ {
+ this.sharedDiv = null;
+ }
+ }
+};
diff --git a/src/js/handler/mxSelectionCellsHandler.js b/src/js/handler/mxSelectionCellsHandler.js
new file mode 100644
index 0000000..800d718
--- /dev/null
+++ b/src/js/handler/mxSelectionCellsHandler.js
@@ -0,0 +1,260 @@
+/**
+ * $Id: mxSelectionCellsHandler.js,v 1.5 2012-08-10 11:35:06 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSelectionCellsHandler
+ *
+ * An event handler that manages cell handlers and invokes their mouse event
+ * processing functions.
+ *
+ * Group: Events
+ *
+ * Event: mxEvent.ADD
+ *
+ * Fires if a cell has been added to the selection. The <code>state</code>
+ * property contains the <mxCellState> that has been added.
+ *
+ * Event: mxEvent.REMOVE
+ *
+ * Fires if a cell has been remove from the selection. The <code>state</code>
+ * property contains the <mxCellState> that has been removed.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxSelectionCellsHandler(graph)
+{
+ this.graph = graph;
+ this.handlers = new mxDictionary();
+ this.graph.addMouseListener(this);
+
+ this.refreshHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.refresh();
+ }
+ });
+
+ this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+ this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+ this.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler);
+ this.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler);
+ this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler);
+ this.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler);
+ this.graph.getView().addListener(mxEvent.UP, this.refreshHandler);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSelectionCellsHandler.prototype = new mxEventSource();
+mxSelectionCellsHandler.prototype.constructor = mxSelectionCellsHandler;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSelectionCellsHandler.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxSelectionCellsHandler.prototype.enabled = true;
+
+/**
+ * Variable: refreshHandler
+ *
+ * Keeps a reference to an event listener for later removal.
+ */
+mxSelectionCellsHandler.prototype.refreshHandler = null;
+
+/**
+ * Variable: maxHandlers
+ *
+ * Defines the maximum number of handlers to paint individually. Default is 100.
+ */
+mxSelectionCellsHandler.prototype.maxHandlers = 100;
+
+/**
+ * Variable: handlers
+ *
+ * <mxDictionary> that maps from cells to handlers.
+ */
+mxSelectionCellsHandler.prototype.handlers = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns <enabled>.
+ */
+mxSelectionCellsHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Sets <enabled>.
+ */
+mxSelectionCellsHandler.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: getHandler
+ *
+ * Returns the handler for the given cell.
+ */
+mxSelectionCellsHandler.prototype.getHandler = function(cell)
+{
+ return this.handlers.get(cell);
+};
+
+/**
+ * Function: reset
+ *
+ * Resets all handlers.
+ */
+mxSelectionCellsHandler.prototype.reset = function()
+{
+ this.handlers.visit(function(key, handler)
+ {
+ handler.reset.apply(handler);
+ });
+};
+
+/**
+ * Function: refresh
+ *
+ * Reloads or updates all handlers.
+ */
+mxSelectionCellsHandler.prototype.refresh = function()
+{
+ // Removes all existing handlers
+ var oldHandlers = this.handlers;
+ this.handlers = new mxDictionary();
+
+ // Creates handles for all selection cells
+ var tmp = this.graph.getSelectionCells();
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ var state = this.graph.view.getState(tmp[i]);
+
+ if (state != null)
+ {
+ var handler = oldHandlers.remove(tmp[i]);
+
+ if (handler != null)
+ {
+ if (handler.state != state)
+ {
+ handler.destroy();
+ handler = null;
+ }
+ else
+ {
+ handler.redraw();
+ }
+ }
+
+ if (handler == null)
+ {
+ handler = this.graph.createHandler(state);
+ this.fireEvent(new mxEventObject(mxEvent.ADD, 'state', state));
+ }
+
+ if (handler != null)
+ {
+ this.handlers.put(tmp[i], handler);
+ }
+ }
+ }
+
+ // Destroys all unused handlers
+ oldHandlers.visit(mxUtils.bind(this, function(key, handler)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE, 'state', handler.state));
+ handler.destroy();
+ }));
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseDown = function(sender, me)
+{
+ if (this.graph.isEnabled() && this.isEnabled())
+ {
+ var args = [sender, me];
+
+ this.handlers.visit(function(key, handler)
+ {
+ handler.mouseDown.apply(handler, args);
+ });
+ }
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseMove = function(sender, me)
+{
+ if (this.graph.isEnabled() && this.isEnabled())
+ {
+ var args = [sender, me];
+
+ this.handlers.visit(function(key, handler)
+ {
+ handler.mouseMove.apply(handler, args);
+ });
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseUp = function(sender, me)
+{
+ if (this.graph.isEnabled() && this.isEnabled())
+ {
+ var args = [sender, me];
+
+ this.handlers.visit(function(key, handler)
+ {
+ handler.mouseUp.apply(handler, args);
+ });
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxSelectionCellsHandler.prototype.destroy = function()
+{
+ this.graph.removeMouseListener(this);
+
+ if (this.refreshHandler != null)
+ {
+ this.graph.getSelectionModel().removeListener(this.refreshHandler);
+ this.graph.getModel().removeListener(this.refreshHandler);
+ this.graph.getView().removeListener(this.refreshHandler);
+ this.refreshHandler = null;
+ }
+};
diff --git a/src/js/handler/mxTooltipHandler.js b/src/js/handler/mxTooltipHandler.js
new file mode 100644
index 0000000..4e34a13
--- /dev/null
+++ b/src/js/handler/mxTooltipHandler.js
@@ -0,0 +1,317 @@
+/**
+ * $Id: mxTooltipHandler.js,v 1.51 2011-03-31 10:11:17 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxTooltipHandler
+ *
+ * Graph event handler that displays tooltips. <mxGraph.getTooltip> is used to
+ * get the tooltip for a cell or handle. This handler is built-into
+ * <mxGraph.tooltipHandler> and enabled using <mxGraph.setTooltips>.
+ *
+ * Example:
+ *
+ * (code>
+ * new mxTooltipHandler(graph);
+ * (end)
+ *
+ * Constructor: mxTooltipHandler
+ *
+ * Constructs an event handler that displays tooltips with the specified
+ * delay (in milliseconds). If no delay is specified then a default delay
+ * of 500 ms (0.5 sec) is used.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * delay - Optional delay in milliseconds.
+ */
+function mxTooltipHandler(graph, delay)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.delay = delay || 500;
+ this.graph.addMouseListener(this);
+ }
+};
+
+/**
+ * Variable: zIndex
+ *
+ * Specifies the zIndex for the tooltip and its shadow. Default is 10005.
+ */
+mxTooltipHandler.prototype.zIndex = 10005;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxTooltipHandler.prototype.graph = null;
+
+/**
+ * Variable: delay
+ *
+ * Delay to show the tooltip in milliseconds. Default is 500.
+ */
+mxTooltipHandler.prototype.delay = null;
+
+/**
+ * Variable: hideOnHover
+ *
+ * Specifies if the tooltip should be hidden if the mouse is moved over the
+ * current cell. Default is false.
+ */
+mxTooltipHandler.prototype.hideOnHover = false;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxTooltipHandler.prototype.enabled = true;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxTooltipHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ */
+mxTooltipHandler.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isHideOnHover
+ *
+ * Returns <hideOnHover>.
+ */
+mxTooltipHandler.prototype.isHideOnHover = function()
+{
+ return this.hideOnHover;
+};
+
+/**
+ * Function: setHideOnHover
+ *
+ * Sets <hideOnHover>.
+ */
+mxTooltipHandler.prototype.setHideOnHover = function(value)
+{
+ this.hideOnHover = value;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the DOM nodes required for this tooltip handler.
+ */
+mxTooltipHandler.prototype.init = function()
+{
+ if (document.body != null)
+ {
+ this.div = document.createElement('div');
+ this.div.className = 'mxTooltip';
+ this.div.style.visibility = 'hidden';
+ this.div.style.zIndex = this.zIndex;
+
+ document.body.appendChild(this.div);
+
+ mxEvent.addListener(this.div, 'mousedown',
+ mxUtils.bind(this, function(evt)
+ {
+ this.hideTooltip();
+ })
+ );
+ }
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by initiating a rubberband selection. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxTooltipHandler.prototype.mouseDown = function(sender, me)
+{
+ this.reset(me, false);
+ this.hideTooltip();
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating the rubberband selection.
+ */
+mxTooltipHandler.prototype.mouseMove = function(sender, me)
+{
+ if (me.getX() != this.lastX || me.getY() != this.lastY)
+ {
+ this.reset(me, true);
+
+ if (this.isHideOnHover() || me.getState() != this.state || (me.getSource() != this.node &&
+ (!this.stateSource || (me.getState() != null && this.stateSource ==
+ (me.isSource(me.getState().shape) || !me.isSource(me.getState().text))))))
+ {
+ this.hideTooltip();
+ }
+ }
+
+ this.lastX = me.getX();
+ this.lastY = me.getY();
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by resetting the tooltip timer or hiding the existing
+ * tooltip.
+ */
+mxTooltipHandler.prototype.mouseUp = function(sender, me)
+{
+ this.reset(me, true);
+ this.hideTooltip();
+};
+
+
+/**
+ * Function: resetTimer
+ *
+ * Resets the timer.
+ */
+mxTooltipHandler.prototype.resetTimer = function()
+{
+ if (this.thread != null)
+ {
+ window.clearTimeout(this.thread);
+ this.thread = null;
+ }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets and/or restarts the timer to trigger the display of the tooltip.
+ */
+mxTooltipHandler.prototype.reset = function(me, restart)
+{
+ this.resetTimer();
+
+ if (restart && this.isEnabled() && me.getState() != null && (this.div == null ||
+ this.div.style.visibility == 'hidden'))
+ {
+ var state = me.getState();
+ var node = me.getSource();
+ var x = me.getX();
+ var y = me.getY();
+ var stateSource = me.isSource(state.shape) || me.isSource(state.text);
+
+ this.thread = window.setTimeout(mxUtils.bind(this, function()
+ {
+ if (!this.graph.isEditing() && !this.graph.panningHandler.isMenuShowing())
+ {
+ // Uses information from inside event cause using the event at
+ // this (delayed) point in time is not possible in IE as it no
+ // longer contains the required information (member not found)
+ var tip = this.graph.getTooltip(state, node, x, y);
+ this.show(tip, x, y);
+ this.state = state;
+ this.node = node;
+ this.stateSource = stateSource;
+ }
+ }), this.delay);
+ }
+};
+
+/**
+ * Function: hide
+ *
+ * Hides the tooltip and resets the timer.
+ */
+mxTooltipHandler.prototype.hide = function()
+{
+ this.resetTimer();
+ this.hideTooltip();
+};
+
+/**
+ * Function: hideTooltip
+ *
+ * Hides the tooltip.
+ */
+mxTooltipHandler.prototype.hideTooltip = function()
+{
+ if (this.div != null)
+ {
+ this.div.style.visibility = 'hidden';
+ }
+};
+
+/**
+ * Function: show
+ *
+ * Shows the tooltip for the specified cell and optional index at the
+ * specified location (with a vertical offset of 10 pixels).
+ */
+mxTooltipHandler.prototype.show = function(tip, x, y)
+{
+ if (tip != null && tip.length > 0)
+ {
+ // Initializes the DOM nodes if required
+ if (this.div == null)
+ {
+ this.init();
+ }
+
+ var origin = mxUtils.getScrollOrigin();
+
+ this.div.style.left = (x + origin.x) + 'px';
+ this.div.style.top = (y + mxConstants.TOOLTIP_VERTICAL_OFFSET +
+ origin.y) + 'px';
+
+ if (!mxUtils.isNode(tip))
+ {
+ this.div.innerHTML = tip.replace(/\n/g, '<br>');
+ }
+ else
+ {
+ this.div.innerHTML = '';
+ this.div.appendChild(tip);
+ }
+
+ this.div.style.visibility = '';
+ mxUtils.fit(this.div);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxTooltipHandler.prototype.destroy = function()
+{
+ this.graph.removeMouseListener(this);
+ mxEvent.release(this.div);
+
+ if (this.div != null && this.div.parentNode != null)
+ {
+ this.div.parentNode.removeChild(this.div);
+ }
+
+ this.div = null;
+};
diff --git a/src/js/handler/mxVertexHandler.js b/src/js/handler/mxVertexHandler.js
new file mode 100644
index 0000000..0b12e27
--- /dev/null
+++ b/src/js/handler/mxVertexHandler.js
@@ -0,0 +1,753 @@
+/**
+ * $Id: mxVertexHandler.js,v 1.107 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxVertexHandler
+ *
+ * Event handler for resizing cells. This handler is automatically created in
+ * <mxGraph.createHandler>.
+ *
+ * Constructor: mxVertexHandler
+ *
+ * Constructs an event handler that allows to resize vertices
+ * and groups.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the cell to be resized.
+ */
+function mxVertexHandler(state)
+{
+ if (state != null)
+ {
+ this.state = state;
+ this.init();
+ }
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxVertexHandler.prototype.graph = null;
+
+/**
+ * Variable: state
+ *
+ * Reference to the <mxCellState> being modified.
+ */
+mxVertexHandler.prototype.state = null;
+
+/**
+ * Variable: singleSizer
+ *
+ * Specifies if only one sizer handle at the bottom, right corner should be
+ * used. Default is false.
+ */
+mxVertexHandler.prototype.singleSizer = false;
+
+/**
+ * Variable: index
+ *
+ * Holds the index of the current handle.
+ */
+mxVertexHandler.prototype.index = null;
+
+/**
+ * Variable: allowHandleBoundsCheck
+ *
+ * Specifies if the bounds of handles should be used for hit-detection in IE
+ * Default is true.
+ */
+mxVertexHandler.prototype.allowHandleBoundsCheck = true;
+
+/**
+ * Variable: crisp
+ *
+ * Specifies if the selection bounds and handles should be rendered in crisp
+ * mode. Default is true.
+ */
+mxVertexHandler.prototype.crisp = true;
+
+/**
+ * Variable: handleImage
+ *
+ * Optional <mxImage> to be used as handles. Default is null.
+ */
+mxVertexHandler.prototype.handleImage = null;
+
+/**
+ * Variable: tolerance
+ *
+ * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
+ */
+mxVertexHandler.prototype.tolerance = 0;
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this vertex handler.
+ */
+mxVertexHandler.prototype.init = function()
+{
+ this.graph = this.state.view.graph;
+ this.selectionBounds = this.getSelectionBounds(this.state);
+ this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,
+ this.selectionBounds.width, this.selectionBounds.height);
+ this.selectionBorder = this.createSelectionShape(this.bounds);
+ this.selectionBorder.dialect =
+ (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.selectionBorder.init(this.graph.getView().getOverlayPane());
+
+ // Event-transparency
+ if (this.selectionBorder.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.selectionBorder.node.setAttribute('pointer-events', 'none');
+ }
+ else
+ {
+ this.selectionBorder.node.style.background = '';
+ }
+
+ if (this.graph.isCellMovable(this.state.cell))
+ {
+ this.selectionBorder.node.style.cursor = mxConstants.CURSOR_MOVABLE_VERTEX;
+ }
+
+ mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state);
+
+ // Adds the sizer handles
+ if (mxGraphHandler.prototype.maxCells <= 0 ||
+ this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells)
+ {
+ var resizable = this.graph.isCellResizable(this.state.cell);
+ this.sizers = [];
+
+ if (resizable || (this.graph.isLabelMovable(this.state.cell) &&
+ this.state.width >= 2 && this.state.height >= 2))
+ {
+ var i = 0;
+
+ if (resizable)
+ {
+ if (!this.singleSizer)
+ {
+ this.sizers.push(this.createSizer('nw-resize', i++));
+ this.sizers.push(this.createSizer('n-resize', i++));
+ this.sizers.push(this.createSizer('ne-resize', i++));
+ this.sizers.push(this.createSizer('w-resize', i++));
+ this.sizers.push(this.createSizer('e-resize', i++));
+ this.sizers.push(this.createSizer('sw-resize', i++));
+ this.sizers.push(this.createSizer('s-resize', i++));
+ }
+
+ this.sizers.push(this.createSizer('se-resize', i++));
+ }
+
+ var geo = this.graph.model.getGeometry(this.state.cell);
+
+ if (geo != null && !geo.relative && !this.graph.isSwimlane(this.state.cell) &&
+ this.graph.isLabelMovable(this.state.cell))
+ {
+ // Marks this as the label handle for getHandleForEvent
+ this.labelShape = this.createSizer(mxConstants.CURSOR_LABEL_HANDLE,
+ mxEvent.LABEL_HANDLE, mxConstants.LABEL_HANDLE_SIZE,
+ mxConstants.LABEL_HANDLE_FILLCOLOR);
+ this.sizers.push(this.labelShape);
+ }
+ }
+ else if (this.graph.isCellMovable(this.state.cell) && !this.graph.isCellResizable(this.state.cell) &&
+ this.state.width < 2 && this.state.height < 2)
+ {
+ this.labelShape = this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX,
+ null, null, mxConstants.LABEL_HANDLE_FILLCOLOR);
+ this.sizers.push(this.labelShape);
+ }
+ }
+
+ this.redraw();
+};
+
+/**
+ * Function: getSelectionBounds
+ *
+ * Returns the mxRectangle that defines the bounds of the selection
+ * border.
+ */
+mxVertexHandler.prototype.getSelectionBounds = function(state)
+{
+ return new mxRectangle(state.x, state.y, state.width, state.height);
+};
+
+/**
+ * Function: createSelectionShape
+ *
+ * Creates the shape used to draw the selection border.
+ */
+mxVertexHandler.prototype.createSelectionShape = function(bounds)
+{
+ var shape = new mxRectangleShape(bounds, null, this.getSelectionColor());
+ shape.strokewidth = this.getSelectionStrokeWidth();
+ shape.isDashed = this.isSelectionDashed();
+ shape.crisp = this.crisp;
+
+ return shape;
+};
+
+/**
+ * Function: getSelectionColor
+ *
+ * Returns <mxConstants.VERTEX_SELECTION_COLOR>.
+ */
+mxVertexHandler.prototype.getSelectionColor = function()
+{
+ return mxConstants.VERTEX_SELECTION_COLOR;
+};
+
+/**
+ * Function: getSelectionStrokeWidth
+ *
+ * Returns <mxConstants.VERTEX_SELECTION_STROKEWIDTH>.
+ */
+mxVertexHandler.prototype.getSelectionStrokeWidth = function()
+{
+ return mxConstants.VERTEX_SELECTION_STROKEWIDTH;
+};
+
+/**
+ * Function: isSelectionDashed
+ *
+ * Returns <mxConstants.VERTEX_SELECTION_DASHED>.
+ */
+mxVertexHandler.prototype.isSelectionDashed = function()
+{
+ return mxConstants.VERTEX_SELECTION_DASHED;
+};
+
+/**
+ * Function: createSizer
+ *
+ * Creates a sizer handle for the specified cursor and index and returns
+ * the new <mxRectangleShape> that represents the handle.
+ */
+mxVertexHandler.prototype.createSizer = function(cursor, index, size, fillColor)
+{
+ size = size || mxConstants.HANDLE_SIZE;
+
+ var bounds = new mxRectangle(0, 0, size, size);
+ var sizer = this.createSizerShape(bounds, index, fillColor);
+
+ if (this.state.text != null && this.state.text.node.parentNode == this.graph.container)
+ {
+ sizer.bounds.height -= 1;
+ sizer.bounds.width -= 1;
+ sizer.dialect = mxConstants.DIALECT_STRICTHTML;
+ sizer.init(this.graph.container);
+ }
+ else
+ {
+ sizer.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ sizer.init(this.graph.getView().getOverlayPane());
+ }
+
+ mxEvent.redirectMouseEvents(sizer.node, this.graph, this.state);
+
+ if (this.graph.isEnabled())
+ {
+ sizer.node.style.cursor = cursor;
+ }
+
+ if (!this.isSizerVisible(index))
+ {
+ sizer.node.style.visibility = 'hidden';
+ }
+
+ return sizer;
+};
+
+/**
+ * Function: isSizerVisible
+ *
+ * Returns true if the sizer for the given index is visible.
+ * This returns true for all given indices.
+ */
+mxVertexHandler.prototype.isSizerVisible = function(index)
+{
+ return true;
+};
+
+/**
+ * Function: createSizerShape
+ *
+ * Creates the shape used for the sizer handle for the specified bounds and
+ * index.
+ */
+mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor)
+{
+ if (this.handleImage != null)
+ {
+ bounds.width = this.handleImage.width;
+ bounds.height = this.handleImage.height;
+
+ return new mxImageShape(bounds, this.handleImage.src);
+ }
+ else
+ {
+ var shape = new mxRectangleShape(bounds,
+ fillColor || mxConstants.HANDLE_FILLCOLOR,
+ mxConstants.HANDLE_STROKECOLOR);
+ shape.crisp = this.crisp;
+
+ return shape;
+ }
+};
+
+/**
+ * Function: createBounds
+ *
+ * Helper method to create an <mxRectangle> around the given centerpoint
+ * with a width and height of 2*s or 6, if no s is given.
+ */
+mxVertexHandler.prototype.moveSizerTo = function(shape, x, y)
+{
+ if (shape != null)
+ {
+ shape.bounds.x = x - shape.bounds.width / 2;
+ shape.bounds.y = y - shape.bounds.height / 2;
+ shape.redraw();
+ }
+};
+
+/**
+ * Function: getHandleForEvent
+ *
+ * Returns the index of the handle for the given event. This returns the index
+ * of the sizer from where the event originated or <mxEvent.LABEL_INDEX>.
+ */
+mxVertexHandler.prototype.getHandleForEvent = function(me)
+{
+ if (me.isSource(this.labelShape))
+ {
+ return mxEvent.LABEL_HANDLE;
+ }
+
+ if (this.sizers != null)
+ {
+ // Connection highlight may consume events before they reach sizer handle
+ var tol = this.tolerance;
+ var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+ new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+
+ for (var i = 0; i < this.sizers.length; i++)
+ {
+ if (me.isSource(this.sizers[i]) || (hit != null &&
+ this.sizers[i].node.style.visibility != 'hidden' &&
+ mxUtils.intersects(this.sizers[i].bounds, hit)))
+ {
+ return i;
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event if a handle has been clicked. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxVertexHandler.prototype.mouseDown = function(sender, me)
+{
+ if (!me.isConsumed() && this.graph.isEnabled() && !this.graph.isForceMarqueeEvent(me.getEvent()) &&
+ (this.tolerance > 0 || me.getState() == this.state))
+ {
+ var handle = this.getHandleForEvent(me);
+
+ if (handle != null)
+ {
+ this.start(me.getX(), me.getY(), handle);
+ me.consume();
+ }
+ }
+};
+
+/**
+ * Function: start
+ *
+ * Starts the handling of the mouse gesture.
+ */
+mxVertexHandler.prototype.start = function(x, y, index)
+{
+ var pt = mxUtils.convertPoint(this.graph.container, x, y);
+ this.startX = pt.x;
+ this.startY = pt.y;
+ this.index = index;
+
+ // Creates a preview that can be on top of any HTML label
+ this.selectionBorder.node.style.visibility = 'hidden';
+ this.preview = this.createSelectionShape(this.bounds);
+
+ if (this.state.text != null && this.state.text.node.parentNode == this.graph.container)
+ {
+ this.preview.dialect = mxConstants.DIALECT_STRICTHTML;
+ this.preview.init(this.graph.container);
+ }
+ else
+ {
+ this.preview.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.preview.init(this.graph.view.getOverlayPane());
+ }
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating the preview.
+ */
+mxVertexHandler.prototype.mouseMove = function(sender, me)
+{
+ if (!me.isConsumed() && this.index != null)
+ {
+ var point = new mxPoint(me.getGraphX(), me.getGraphY());
+ var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
+ var scale = this.graph.getView().scale;
+
+ if (this.index == mxEvent.LABEL_HANDLE)
+ {
+ if (gridEnabled)
+ {
+ point.x = this.graph.snap(point.x / scale) * scale;
+ point.y = this.graph.snap(point.y / scale) * scale;
+ }
+
+ this.moveSizerTo(this.sizers[this.sizers.length - 1], point.x, point.y);
+ me.consume();
+ }
+ else if (this.index != null)
+ {
+ var dx = point.x - this.startX;
+ var dy = point.y - this.startY;
+ var tr = this.graph.view.translate;
+ this.bounds = this.union(this.selectionBounds, dx, dy, this.index, gridEnabled, scale, tr);
+ this.drawPreview();
+ me.consume();
+ }
+ }
+ // Workaround for disabling the connect highlight when over handle
+ else if (this.getHandleForEvent(me) != null)
+ {
+ me.consume(false);
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by applying the changes to the geometry.
+ */
+mxVertexHandler.prototype.mouseUp = function(sender, me)
+{
+ if (!me.isConsumed() && this.index != null && this.state != null)
+ {
+ var point = new mxPoint(me.getGraphX(), me.getGraphY());
+ var scale = this.graph.getView().scale;
+
+ var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
+ var dx = (point.x - this.startX) / scale;
+ var dy = (point.y - this.startY) / scale;
+
+ this.resizeCell(this.state.cell, dx, dy, this.index, gridEnabled);
+ this.reset();
+ me.consume();
+ }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this handler.
+ */
+mxVertexHandler.prototype.reset = function()
+{
+ this.index = null;
+
+ if (this.preview != null)
+ {
+ this.preview.destroy();
+ this.preview = null;
+ }
+
+ // Checks if handler has been destroyed
+ if (this.selectionBorder != null)
+ {
+ this.selectionBounds = this.getSelectionBounds(this.state);
+ this.selectionBorder.node.style.visibility = 'visible';
+ this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,
+ this.selectionBounds.width, this.selectionBounds.height);
+ this.drawPreview();
+ }
+};
+
+/**
+ * Function: resizeCell
+ *
+ * Uses the given vector to change the bounds of the given cell
+ * in the graph using <mxGraph.resizeCell>.
+ */
+mxVertexHandler.prototype.resizeCell = function(cell, dx, dy, index, gridEnabled)
+{
+ var geo = this.graph.model.getGeometry(cell);
+
+ if (index == mxEvent.LABEL_HANDLE)
+ {
+ var scale = this.graph.view.scale;
+ dx = (this.labelShape.bounds.getCenterX() - this.startX) / scale;
+ dy = (this.labelShape.bounds.getCenterY() - this.startY) / scale;
+
+ geo = geo.clone();
+
+ if (geo.offset == null)
+ {
+ geo.offset = new mxPoint(dx, dy);
+ }
+ else
+ {
+ geo.offset.x += dx;
+ geo.offset.y += dy;
+ }
+
+ this.graph.model.setGeometry(cell, geo);
+ }
+ else
+ {
+ var bounds = this.union(geo, dx, dy, index, gridEnabled, 1, new mxPoint(0, 0));
+ this.graph.resizeCell(cell, bounds);
+ }
+};
+
+/**
+ * Function: union
+ *
+ * Returns the union of the given bounds and location for the specified
+ * handle index.
+ *
+ * To override this to limit the size of vertex via a minWidth/-Height style,
+ * the following code can be used.
+ *
+ * (code)
+ * var vertexHandlerUnion = mxVertexHandler.prototype.union;
+ * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr)
+ * {
+ * var result = vertexHandlerUnion.apply(this, arguments);
+ *
+ * result.width = Math.max(result.width, mxUtils.getNumber(this.state.style, 'minWidth', 0));
+ * result.height = Math.max(result.height, mxUtils.getNumber(this.state.style, 'minHeight', 0));
+ *
+ * return result;
+ * };
+ * (end)
+ *
+ * The minWidth/-Height style can then be used as follows:
+ *
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'minWidth=100;minHeight=100;');
+ * (end)
+ */
+mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr)
+{
+ if (this.singleSizer)
+ {
+ var x = bounds.x + bounds.width + dx;
+ var y = bounds.y + bounds.height + dy;
+
+ if (gridEnabled)
+ {
+ x = this.graph.snap(x / scale) * scale;
+ y = this.graph.snap(y / scale) * scale;
+ }
+
+ var rect = new mxRectangle(bounds.x, bounds.y, 0, 0);
+ rect.add(new mxRectangle(x, y, 0, 0));
+
+ return rect;
+ }
+ else
+ {
+ var left = bounds.x - tr.x * scale;
+ var right = left + bounds.width;
+ var top = bounds.y - tr.y * scale;
+ var bottom = top + bounds.height;
+
+ if (index > 4 /* Bottom Row */)
+ {
+ bottom = bottom + dy;
+
+ if (gridEnabled)
+ {
+ bottom = this.graph.snap(bottom / scale) * scale;
+ }
+ }
+ else if (index < 3 /* Top Row */)
+ {
+ top = top + dy;
+
+ if (gridEnabled)
+ {
+ top = this.graph.snap(top / scale) * scale;
+ }
+ }
+
+ if (index == 0 || index == 3 || index == 5 /* Left */)
+ {
+ left += dx;
+
+ if (gridEnabled)
+ {
+ left = this.graph.snap(left / scale) * scale;
+ }
+ }
+ else if (index == 2 || index == 4 || index == 7 /* Right */)
+ {
+ right += dx;
+
+ if (gridEnabled)
+ {
+ right = this.graph.snap(right / scale) * scale;
+ }
+ }
+
+ var width = right - left;
+ var height = bottom - top;
+
+ // Flips over left side
+ if (width < 0)
+ {
+ left += width;
+ width = Math.abs(width);
+ }
+
+ // Flips over top side
+ if (height < 0)
+ {
+ top += height;
+ height = Math.abs(height);
+ }
+
+ return new mxRectangle(left + tr.x * scale, top + tr.y * scale, width, height);
+ }
+};
+
+/**
+ * Function: redraw
+ *
+ * Redraws the handles and the preview.
+ */
+mxVertexHandler.prototype.redraw = function()
+{
+ this.selectionBounds = this.getSelectionBounds(this.state);
+ this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,
+ this.selectionBounds.width, this.selectionBounds.height);
+
+ if (this.sizers != null)
+ {
+ var s = this.state;
+ var r = s.x + s.width;
+ var b = s.y + s.height;
+
+ if (this.singleSizer)
+ {
+ this.moveSizerTo(this.sizers[0], r, b);
+ }
+ else
+ {
+ var cx = s.x + s.width / 2;
+ var cy = s.y + s.height / 2;
+
+ if (this.sizers.length > 1)
+ {
+ this.moveSizerTo(this.sizers[0], s.x, s.y);
+ this.moveSizerTo(this.sizers[1], cx, s.y);
+ this.moveSizerTo(this.sizers[2], r, s.y);
+ this.moveSizerTo(this.sizers[3], s.x, cy);
+ this.moveSizerTo(this.sizers[4], r, cy);
+ this.moveSizerTo(this.sizers[5], s.x, b);
+ this.moveSizerTo(this.sizers[6], cx, b);
+ this.moveSizerTo(this.sizers[7], r, b);
+ this.moveSizerTo(this.sizers[8],
+ cx + s.absoluteOffset.x,
+ cy + s.absoluteOffset.y);
+ }
+ else if (this.state.width >= 2 && this.state.height >= 2)
+ {
+ this.moveSizerTo(this.sizers[0],
+ cx + s.absoluteOffset.x,
+ cy + s.absoluteOffset.y);
+ }
+ else
+ {
+ this.moveSizerTo(this.sizers[0], s.x, s.y);
+ }
+ }
+ }
+
+ this.drawPreview();
+};
+
+/**
+ * Function: drawPreview
+ *
+ * Redraws the preview.
+ */
+mxVertexHandler.prototype.drawPreview = function()
+{
+ if (this.preview != null)
+ {
+ this.preview.bounds = this.bounds;
+
+ if (this.preview.node.parentNode == this.graph.container)
+ {
+ this.preview.bounds.width = Math.max(0, this.preview.bounds.width - 1);
+ this.preview.bounds.height = Math.max(0, this.preview.bounds.height - 1);
+ }
+
+ this.preview.redraw();
+ }
+
+ this.selectionBorder.bounds = this.bounds;
+ this.selectionBorder.redraw();
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxVertexHandler.prototype.destroy = function()
+{
+ if (this.preview != null)
+ {
+ this.preview.destroy();
+ this.preview = null;
+ }
+
+ this.selectionBorder.destroy();
+ this.selectionBorder = null;
+ this.labelShape = null;
+
+ if (this.sizers != null)
+ {
+ for (var i = 0; i < this.sizers.length; i++)
+ {
+ this.sizers[i].destroy();
+ this.sizers[i] = null;
+ }
+ }
+};
diff --git a/src/js/index.txt b/src/js/index.txt
new file mode 100644
index 0000000..f3631d6
--- /dev/null
+++ b/src/js/index.txt
@@ -0,0 +1,316 @@
+Document: API Specification
+
+Overview:
+
+ This JavaScript library is divided into 8 packages. The top-level <mxClient>
+ class includes (or dynamically imports) everything else. The current version
+ is stored in <mxClient.VERSION>.
+
+ The *editor* package provides the classes required to implement a diagram
+ editor. The main class in this package is <mxEditor>.
+
+ The *view* and *model* packages implement the graph component, represented
+ by <mxGraph>. It refers to a <mxGraphModel> which contains <mxCell>s and
+ caches the state of the cells in a <mxGraphView>. The cells are painted
+ using a <mxCellRenderer> based on the appearance defined in <mxStylesheet>.
+ Undo history is implemented in <mxUndoManager>. To display an icon on the
+ graph, <mxCellOverlay> may be used. Validation rules are defined with
+ <mxMultiplicity>.
+
+ The *handler*, *layout* and *shape* packages contain event listeners,
+ layout algorithms and shapes, respectively. The graph event listeners
+ include <mxRubberband> for rubberband selection, <mxTooltipHandler>
+ for tooltips and <mxGraphHandler> for basic cell modifications.
+ <mxCompactTreeLayout> implements a tree layout algorithm, and the
+ shape package provides various shapes, which are subclasses of
+ <mxShape>.
+
+ The *util* package provides utility classes including <mxClipboard> for
+ copy-paste, <mxDatatransfer> for drag-and-drop, <mxConstants> for keys and
+ values of stylesheets, <mxEvent> and <mxUtils> for cross-browser
+ event-handling and general purpose functions, <mxResources> for
+ internationalization and <mxLog> for console output.
+
+ The *io* package implements a generic <mxObjectCodec> for turning
+ JavaScript objects into XML. The main class is <mxCodec>.
+ <mxCodecRegistry> is the global registry for custom codecs.
+
+Events:
+
+ There are three different types of events, namely native DOM events,
+ <mxEventObjects> which are fired in an <mxEventSource>, and <mxMouseEvents>
+ which are fired in <mxGraph>.
+
+ Some helper methods for handling native events are provided in <mxEvent>. It
+ also takes care of resolving cycles between DOM nodes and JavaScript event
+ handlers, which can lead to memory leaks in IE6.
+
+ Most custom events in mxGraph are implemented using <mxEventSource>. Its
+ listeners are functions that take a sender and <mxEventObject>. Additionally,
+ the <mxGraph> class fires special <mxMouseEvents> which are handled using
+ mouse listeners, which are objects that provide a mousedown, mousemove and
+ mouseup method.
+
+ Events in <mxEventSource> are fired using <mxEventSource.fireEvent>.
+ Listeners are added and removed using <mxEventSource.addListener> and
+ <mxEventSource.removeListener>. <mxMouseEvents> in <mxGraph> are fired using
+ <mxGraph.fireMouseEvent>. Listeners are added and removed using
+ <mxGraph.addMouseListener> and <mxGraph.removeMouseListener>, respectively.
+
+Key bindings:
+
+ The following key bindings are defined for mouse events in the client across
+ all browsers and platforms:
+
+ - Control-Drag: Duplicates (clones) selected cells
+ - Shift-Rightlick: Shows the context menu
+ - Alt-Click: Forces rubberband (aka. marquee)
+ - Control-Select: Toggles the selection state
+ - Shift-Drag: Constrains the offset to one direction
+ - Shift-Control-Drag: Panning (also Shift-Rightdrag)
+
+Configuration:
+
+ The following global variables may be defined before the client is loaded to
+ specify its language or base path, respectively.
+
+ - mxBasePath: Specifies the path in <mxClient.basePath>.
+ - mxImageBasePath: Specifies the path in <mxClient.imageBasePath>.
+ - mxLanguage: Specifies the language for resources in <mxClient.language>.
+ - mxDefaultLanguage: Specifies the default language in <mxClient.defaultLanguage>.
+ - mxLoadResources: Specifies if any resources should be loaded. Default is true.
+ - mxLoadStylesheets: Specifies if any stylesheets should be loaded. Default is true.
+
+Reserved Words:
+
+ The mx prefix is used for all classes and objects in mxGraph. The mx prefix
+ can be seen as the global namespace for all JavaScript code in mxGraph. The
+ following fieldnames should not be used in objects.
+
+ - *mxObjectId*: If the object is used with mxObjectIdentity
+ - *as*: If the object is a field of another object
+ - *id*: If the object is an idref in a codec
+ - *mxListenerList*: Added to DOM nodes when used with <mxEvent>
+ - *window._mxDynamicCode*: Temporarily used to load code in Safari and Chrome
+ (see <mxClient.include>).
+ - *_mxJavaScriptExpression*: Global variable that is temporarily used to
+ evaluate code in Safari, Opera, Firefox 3 and IE (see <mxUtils.eval>).
+
+Files:
+
+ The library contains these relative filenames. All filenames are relative
+ to <mxClient.basePath>.
+
+Built-in Images:
+
+ All images are loaded from the <mxClient.imageBasePath>,
+ which you can change to reflect your environment. The image variables can
+ also be changed individually.
+
+ - mxGraph.prototype.collapsedImage
+ - mxGraph.prototype.expandedImage
+ - mxGraph.prototype.warningImage
+ - mxWindow.prototype.closeImage
+ - mxWindow.prototype.minimizeImage
+ - mxWindow.prototype.normalizeImage
+ - mxWindow.prototype.maximizeImage
+ - mxWindow.prototype.resizeImage
+ - mxPopupMenu.prototype.submenuImage
+ - mxUtils.errorImage
+ - mxConstraintHandler.prototype.pointImage
+
+ The basename of the warning image (images/warning without extension) used in
+ <mxGraph.setCellWarning> is defined in <mxGraph.warningImage>.
+
+Resources:
+
+ The <mxEditor> and <mxGraph> classes add the following resources to
+ <mxResources> at class loading time:
+
+ - resources/editor*.properties
+ - resources/graph*.properties
+
+ By default, the library ships with English and German resource files.
+
+Images:
+
+ Recommendations for using images. Use GIF images (256 color palette) in HTML
+ elements (such as the toolbar and context menu), and PNG images (24 bit) for
+ all images which appear inside the graph component.
+
+ - For PNG images inside HTML elements, Internet Explorer will ignore any
+ transparency information.
+ - For GIF images inside the graph, Firefox on the Mac will display strange
+ colors. Furthermore, only the first image for animated GIFs is displayed
+ on the Mac.
+
+ For faster image rendering during application runtime, images can be
+ prefetched using the following code:
+
+ (code)
+ var image = new Image();
+ image.src = url_to_image;
+ (end)
+
+Deployment:
+
+ The client is added to the page using the following script tag inside the
+ head of a document:
+
+ (code)
+ <script type="text/javascript" src="js/mxClient.js"></script>
+ (end)
+
+ The deployment version of the mxClient.js file contains all required code
+ in a single file. For deployment, the complete javascript/src directory is
+ required.
+
+Source Code:
+
+ If you are a source code customer and you wish to develop using the
+ full source code, the commented source code is shipped in the
+ javascript/devel/source.zip file. It contains one file for each class
+ in mxGraph. To use the source code the source.zip file must be
+ uncompressed and the mxClient.js URL in the HTML page must be changed
+ to reference the uncompressed mxClient.js from the source.zip file.
+
+Compression:
+
+ When using Apache2 with mod_deflate, you can use the following directive
+ in src/js/.htaccess to speedup the loading of the JavaScript sources:
+
+ (code)
+ SetOutputFilter DEFLATE
+ (end)
+
+Classes:
+
+ There are two types of "classes" in mxGraph: classes and singletons (where
+ only one instance exists). Singletons are mapped to global objects where the
+ variable name equals the classname. For example mxConstants is an object with
+ all the constants defined as object fields. Normal classes are mapped to a
+ constructor function and a prototype which defines the instance fields and
+ methods. For example, <mxEditor> is a function and mxEditor.prototype is the
+ prototype for the object that the mxEditor function creates. The mx prefix is
+ a convention that is used for all classes in the mxGraph package to avoid
+ conflicts with other objects in the global namespace.
+
+Subclassing:
+
+ For subclassing, the superclass must provide a constructor that is either
+ parameterless or handles an invocation with no arguments. Furthermore, the
+ special constructor field must be redefined after extending the prototype.
+ For example, the superclass of mxEditor is <mxEventSource>. This is
+ represented in JavaScript by first "inheriting" all fields and methods from
+ the superclass by assigning the prototype to an instance of the superclass,
+ eg. mxEditor.prototype = new mxEventSource() and redefining the constructor
+ field using mxEditor.prototype.constructor = mxEditor. The latter rule is
+ applied so that the type of an object can be retrieved via the name of it’s
+ constructor using mxUtils.getFunctionName(obj.constructor).
+
+Constructor:
+
+ For subclassing in mxGraph, the same scheme should be applied. For example,
+ for subclassing the <mxGraph> class, first a constructor must be defined for
+ the new class. The constructor calls the super constructor with any arguments
+ that it may have using the call function on the mxGraph function object,
+ passing along explitely each argument:
+
+ (code)
+ function MyGraph(container)
+ {
+ mxGraph.call(this, container);
+ }
+ (end)
+
+ The prototype of MyGraph inherits from mxGraph as follows. As usual, the
+ constructor is redefined after extending the superclass:
+
+ (code)
+ MyGraph.prototype = new mxGraph();
+ MyGraph.prototype.constructor = MyGraph;
+ (end)
+
+ You may want to define the codec associated for the class after the above
+ code. This code will be executed at class loading time and makes sure the
+ same codec is used to encode instances of mxGraph and MyGraph.
+
+ (code)
+ var codec = mxCodecRegistry.getCodec(mxGraph);
+ codec.template = new MyGraph();
+ mxCodecRegistry.register(codec);
+ (end)
+
+Functions:
+
+ In the prototype for MyGraph, functions of mxGraph can then be extended as
+ follows.
+
+ (code)
+ MyGraph.prototype.isCellSelectable = function(cell)
+ {
+ var selectable = mxGraph.prototype.isSelectable.apply(this, arguments);
+
+ var geo = this.model.getGeometry(cell);
+ return selectable && (geo == null || !geo.relative);
+ }
+ (end)
+
+ The supercall in the first line is optional. It is done using the apply
+ function on the isSelectable function object of the mxGraph prototype, using
+ the special this and arguments variables as parameters. Calls to the
+ superclass function are only possible if the function is not replaced in the
+ superclass as follows, which is another way of “subclassing” in JavaScript.
+
+ (code)
+ mxGraph.prototype.isCellSelectable = function(cell)
+ {
+ var geo = this.model.getGeometry(cell);
+ return selectable &&
+ (geo == null ||
+ !geo.relative);
+ }
+ (end)
+
+ The above scheme is useful if a function definition needs to be replaced
+ completely.
+
+ In order to add new functions and fields to the subclass, the following code
+ is used. The example below adds a new function to return the XML
+ representation of the graph model:
+
+ (code)
+ MyGraph.prototype.getXml = function()
+ {
+ var enc = new mxCodec();
+ return enc.encode(this.getModel());
+ }
+ (end)
+
+Variables:
+
+ Likewise, a new field is declared and defined as follows.
+
+ (code)
+ MyGraph.prototype.myField = 'Hello, World!';
+ (end)
+
+ Note that the value assigned to myField is created only once, that is, all
+ instances of MyGraph share the same value. If you require instance-specific
+ values, then the field must be defined in the constructor instead.
+
+ (code)
+ function MyGraph(container)
+ {
+ mxGraph.call(this, container);
+
+ this.myField = new Array();
+ }
+ (end)
+
+ Finally, a new instance of MyGraph is created using the following code, where
+ container is a DOM node that acts as a container for the graph view:
+
+ (code)
+ var graph = new MyGraph(container);
+ (end)
diff --git a/src/js/io/mxCellCodec.js b/src/js/io/mxCellCodec.js
new file mode 100644
index 0000000..cbcd651
--- /dev/null
+++ b/src/js/io/mxCellCodec.js
@@ -0,0 +1,170 @@
+/**
+ * $Id: mxCellCodec.js,v 1.22 2010-10-21 07:12:31 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxCellCodec
+ *
+ * Codec for <mxCell>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - children
+ * - edges
+ * - overlays
+ * - mxTransient
+ *
+ * Reference Fields:
+ *
+ * - parent
+ * - source
+ * - target
+ *
+ * Transient fields can be added using the following code:
+ *
+ * mxCodecRegistry.getCodec(mxCell).exclude.push('name_of_field');
+ */
+ var codec = new mxObjectCodec(new mxCell(),
+ ['children', 'edges', 'overlays', 'mxTransient'],
+ ['parent', 'source', 'target']);
+
+ /**
+ * Function: isCellCodec
+ *
+ * Returns true since this is a cell codec.
+ */
+ codec.isCellCodec = function()
+ {
+ return true;
+ };
+
+ /**
+ * Function: isExcluded
+ *
+ * Excludes user objects that are XML nodes.
+ */
+ codec.isExcluded = function(obj, attr, value, isWrite)
+ {
+ return mxObjectCodec.prototype.isExcluded.apply(this, arguments) ||
+ (isWrite && attr == 'value' &&
+ value.nodeType == mxConstants.NODETYPE_ELEMENT);
+ };
+
+ /**
+ * Function: afterEncode
+ *
+ * Encodes an <mxCell> and wraps the XML up inside the
+ * XML of the user object (inversion).
+ */
+ codec.afterEncode = function(enc, obj, node)
+ {
+ if (obj.value != null &&
+ obj.value.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Wraps the graphical annotation up in the user object (inversion)
+ // by putting the result of the default encoding into a clone of the
+ // user object (node type 1) and returning this cloned user object.
+ var tmp = node;
+ node = (mxClient.IS_IE) ?
+ obj.value.cloneNode(true) :
+ enc.document.importNode(obj.value, true);
+ node.appendChild(tmp);
+
+ // Moves the id attribute to the outermost XML node, namely the
+ // node which denotes the object boundaries in the file.
+ var id = tmp.getAttribute('id');
+ node.setAttribute('id', id);
+ tmp.removeAttribute('id');
+ }
+
+ return node;
+ };
+
+ /**
+ * Function: beforeDecode
+ *
+ * Decodes an <mxCell> and uses the enclosing XML node as
+ * the user object for the cell (inversion).
+ */
+ codec.beforeDecode = function(dec, node, obj)
+ {
+ var inner = node;
+ var classname = this.getName();
+
+ if (node.nodeName != classname)
+ {
+ // Passes the inner graphical annotation node to the
+ // object codec for further processing of the cell.
+ var tmp = node.getElementsByTagName(classname)[0];
+
+ if (tmp != null &&
+ tmp.parentNode == node)
+ {
+ mxUtils.removeWhitespace(tmp, true);
+ mxUtils.removeWhitespace(tmp, false);
+ tmp.parentNode.removeChild(tmp);
+ inner = tmp;
+ }
+ else
+ {
+ inner = null;
+ }
+
+ // Creates the user object out of the XML node
+ obj.value = node.cloneNode(true);
+ var id = obj.value.getAttribute('id');
+
+ if (id != null)
+ {
+ obj.setId(id);
+ obj.value.removeAttribute('id');
+ }
+ }
+ else
+ {
+ // Uses ID from XML file as ID for cell in model
+ obj.setId(node.getAttribute('id'));
+ }
+
+ // Preprocesses and removes all Id-references in order to use the
+ // correct encoder (this) for the known references to cells (all).
+ if (inner != null)
+ {
+ for (var i = 0; i < this.idrefs.length; i++)
+ {
+ var attr = this.idrefs[i];
+ var ref = inner.getAttribute(attr);
+
+ if (ref != null)
+ {
+ inner.removeAttribute(attr);
+ var object = dec.objects[ref] || dec.lookup(ref);
+
+ if (object == null)
+ {
+ // Needs to decode forward reference
+ var element = dec.getElementById(ref);
+
+ if (element != null)
+ {
+ var decoder = mxCodecRegistry.codecs[element.nodeName] || this;
+ object = decoder.decode(dec, element);
+ }
+ }
+
+ obj[attr] = object;
+ }
+ }
+ }
+
+ return inner;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxChildChangeCodec.js b/src/js/io/mxChildChangeCodec.js
new file mode 100644
index 0000000..deeb57b
--- /dev/null
+++ b/src/js/io/mxChildChangeCodec.js
@@ -0,0 +1,149 @@
+/**
+ * $Id: mxChildChangeCodec.js,v 1.12 2010-09-15 14:38:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxChildChangeCodec
+ *
+ * Codec for <mxChildChange>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec> and
+ * the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ * - previousIndex
+ * - child
+ *
+ * Reference Fields:
+ *
+ * - parent
+ */
+ var codec = new mxObjectCodec(new mxChildChange(),
+ ['model', 'child', 'previousIndex'],
+ ['parent', 'previous']);
+
+ /**
+ * Function: isReference
+ *
+ * Returns true for the child attribute if the child
+ * cell had a previous parent or if we're reading the
+ * child as an attribute rather than a child node, in
+ * which case it's always a reference.
+ */
+ codec.isReference = function(obj, attr, value, isWrite)
+ {
+ if (attr == 'child' &&
+ (obj.previous != null ||
+ !isWrite))
+ {
+ return true;
+ }
+
+ return mxUtils.indexOf(this.idrefs, attr) >= 0;
+ };
+
+ /**
+ * Function: afterEncode
+ *
+ * Encodes the child recusively and adds the result
+ * to the given node.
+ */
+ codec.afterEncode = function(enc, obj, node)
+ {
+ if (this.isReference(obj, 'child', obj.child, true))
+ {
+ // Encodes as reference (id)
+ node.setAttribute('child', enc.getId(obj.child));
+ }
+ else
+ {
+ // At this point, the encoder is no longer able to know which cells
+ // are new, so we have to encode the complete cell hierarchy and
+ // ignore the ones that are already there at decoding time. Note:
+ // This can only be resolved by moving the notify event into the
+ // execute of the edit.
+ enc.encodeCell(obj.child, node);
+ }
+
+ return node;
+ };
+
+ /**
+ * Function: beforeDecode
+ *
+ * Decodes the any child nodes as using the respective
+ * codec from the registry.
+ */
+ codec.beforeDecode = function(dec, node, obj)
+ {
+ if (node.firstChild != null &&
+ node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Makes sure the original node isn't modified
+ node = node.cloneNode(true);
+
+ var tmp = node.firstChild;
+ obj.child = dec.decodeCell(tmp, false);
+
+ var tmp2 = tmp.nextSibling;
+ tmp.parentNode.removeChild(tmp);
+ tmp = tmp2;
+
+ while (tmp != null)
+ {
+ tmp2 = tmp.nextSibling;
+
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Ignores all existing cells because those do not need to
+ // be re-inserted into the model. Since the encoded version
+ // of these cells contains the new parent, this would leave
+ // to an inconsistent state on the model (ie. a parent
+ // change without a call to parentForCellChanged).
+ var id = tmp.getAttribute('id');
+
+ if (dec.lookup(id) == null)
+ {
+ dec.decodeCell(tmp);
+ }
+ }
+
+ tmp.parentNode.removeChild(tmp);
+ tmp = tmp2;
+ }
+ }
+ else
+ {
+ var childRef = node.getAttribute('child');
+ obj.child = dec.getObject(childRef);
+ }
+
+ return node;
+ };
+
+ /**
+ * Function: afterDecode
+ *
+ * Restores object state in the child change.
+ */
+ codec.afterDecode = function(dec, node, obj)
+ {
+ // Cells are encoded here after a complete transaction so the previous
+ // parent must be restored on the cell for the case where the cell was
+ // added. This is needed for the local model to identify the cell as a
+ // new cell and register the ID.
+ obj.child.parent = obj.previous;
+ obj.previous = obj.parent;
+ obj.previousIndex = obj.index;
+
+ return obj;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxCodec.js b/src/js/io/mxCodec.js
new file mode 100644
index 0000000..b8bfc6a
--- /dev/null
+++ b/src/js/io/mxCodec.js
@@ -0,0 +1,531 @@
+/**
+ * $Id: mxCodec.js,v 1.48 2012-01-04 10:01:16 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCodec
+ *
+ * XML codec for JavaScript object graphs. See <mxObjectCodec> for a
+ * description of the general encoding/decoding scheme. This class uses the
+ * codecs registered in <mxCodecRegistry> for encoding/decoding each object.
+ *
+ * References:
+ *
+ * In order to resolve references, especially forward references, the mxCodec
+ * constructor must be given the document that contains the referenced
+ * elements.
+ *
+ * Examples:
+ *
+ * The following code is used to encode a graph model.
+ *
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = mxUtils.getXml(result);
+ * (end)
+ *
+ * Example:
+ *
+ * Using the following code, the selection cells of a graph are encoded and the
+ * output is displayed in a dialog box.
+ *
+ * (code)
+ * var enc = new mxCodec();
+ * var cells = graph.getSelectionCells();
+ * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells)));
+ * (end)
+ *
+ * Newlines in the XML can be coverted to <br>, in which case a '<br>' argument
+ * must be passed to <mxUtils.getXml> as the second argument.
+ *
+ * Example:
+ *
+ * Using the code below, an XML document is decodec into an existing model. The
+ * document may be obtained using one of the functions in mxUtils for loading
+ * an XML file, eg. <mxUtils.get>, or using <mxUtils.parseXml> for parsing an
+ * XML string.
+ *
+ * (code)
+ * var decoder = new mxCodec(doc)
+ * decoder.decode(doc.documentElement, graph.getModel());
+ * (end)
+ *
+ * Debugging:
+ *
+ * For debugging i/o you can use the following code to get the sequence of
+ * encoded objects:
+ *
+ * (code)
+ * var oldEncode = mxCodec.prototype.encode;
+ * mxCodec.prototype.encode = function(obj)
+ * {
+ * mxLog.show();
+ * mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor));
+ *
+ * return oldEncode.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Constructor: mxCodec
+ *
+ * Constructs an XML encoder/decoder for the specified
+ * owner document.
+ *
+ * Parameters:
+ *
+ * document - Optional XML document that contains the data.
+ * If no document is specified then a new document is created
+ * using <mxUtils.createXmlDocument>.
+ */
+function mxCodec(document)
+{
+ this.document = document || mxUtils.createXmlDocument();
+ this.objects = [];
+};
+
+/**
+ * Variable: document
+ *
+ * The owner document of the codec.
+ */
+mxCodec.prototype.document = null;
+
+/**
+ * Variable: objects
+ *
+ * Maps from IDs to objects.
+ */
+mxCodec.prototype.objects = null;
+
+/**
+ * Variable: encodeDefaults
+ *
+ * Specifies if default values should be encoded. Default is false.
+ */
+mxCodec.prototype.encodeDefaults = false;
+
+
+/**
+ * Function: putObject
+ *
+ * Assoiates the given object with the given ID and returns the given object.
+ *
+ * Parameters
+ *
+ * id - ID for the object to be associated with.
+ * obj - Object to be associated with the ID.
+ */
+mxCodec.prototype.putObject = function(id, obj)
+{
+ this.objects[id] = obj;
+
+ return obj;
+};
+
+/**
+ * Function: getObject
+ *
+ * Returns the decoded object for the element with the specified ID in
+ * <document>. If the object is not known then <lookup> is used to find an
+ * object. If no object is found, then the element with the respective ID
+ * from the document is parsed using <decode>.
+ */
+mxCodec.prototype.getObject = function(id)
+{
+ var obj = null;
+
+ if (id != null)
+ {
+ obj = this.objects[id];
+
+ if (obj == null)
+ {
+ obj = this.lookup(id);
+
+ if (obj == null)
+ {
+ var node = this.getElementById(id);
+
+ if (node != null)
+ {
+ obj = this.decode(node);
+ }
+ }
+ }
+ }
+
+ return obj;
+};
+
+/**
+ * Function: lookup
+ *
+ * Hook for subclassers to implement a custom lookup mechanism for cell IDs.
+ * This implementation always returns null.
+ *
+ * Example:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.lookup = function(id)
+ * {
+ * return model.getCell(id);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * id - ID of the object to be returned.
+ */
+mxCodec.prototype.lookup = function(id)
+{
+ return null;
+};
+
+/**
+ * Function: getElementById
+ *
+ * Returns the element with the given ID from <document>. The optional attr
+ * argument specifies the name of the ID attribute. Default is "id". The
+ * XPath expression used to find the element is //*[@attr='arg'] where attr is
+ * the name of the ID attribute and arg is the given id.
+ *
+ * Parameters:
+ *
+ * id - String that contains the ID.
+ * attr - Optional string for the attributename. Default is "id".
+ */
+mxCodec.prototype.getElementById = function(id, attr)
+{
+ attr = (attr != null) ? attr : 'id';
+
+ return mxUtils.findNodeByAttribute(this.document.documentElement, attr, id);
+};
+
+/**
+ * Function: getId
+ *
+ * Returns the ID of the specified object. This implementation
+ * calls <reference> first and if that returns null handles
+ * the object as an <mxCell> by returning their IDs using
+ * <mxCell.getId>. If no ID exists for the given cell, then
+ * an on-the-fly ID is generated using <mxCellPath.create>.
+ *
+ * Parameters:
+ *
+ * obj - Object to return the ID for.
+ */
+mxCodec.prototype.getId = function(obj)
+{
+ var id = null;
+
+ if (obj != null)
+ {
+ id = this.reference(obj);
+
+ if (id == null && obj instanceof mxCell)
+ {
+ id = obj.getId();
+
+ if (id == null)
+ {
+ // Uses an on-the-fly Id
+ id = mxCellPath.create(obj);
+
+ if (id.length == 0)
+ {
+ id = 'root';
+ }
+ }
+ }
+ }
+
+ return id;
+};
+
+/**
+ * Function: reference
+ *
+ * Hook for subclassers to implement a custom method
+ * for retrieving IDs from objects. This implementation
+ * always returns null.
+ *
+ * Example:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.reference = function(obj)
+ * {
+ * return obj.getCustomId();
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * obj - Object whose ID should be returned.
+ */
+mxCodec.prototype.reference = function(obj)
+{
+ return null;
+};
+
+/**
+ * Function: encode
+ *
+ * Encodes the specified object and returns the resulting
+ * XML node.
+ *
+ * Parameters:
+ *
+ * obj - Object to be encoded.
+ */
+mxCodec.prototype.encode = function(obj)
+{
+ var node = null;
+
+ if (obj != null && obj.constructor != null)
+ {
+ var enc = mxCodecRegistry.getCodec(obj.constructor);
+
+ if (enc != null)
+ {
+ node = enc.encode(this, obj);
+ }
+ else
+ {
+ if (mxUtils.isNode(obj))
+ {
+ node = (mxClient.IS_IE) ? obj.cloneNode(true) :
+ this.document.importNode(obj, true);
+ }
+ else
+ {
+ mxLog.warn('mxCodec.encode: No codec for '+
+ mxUtils.getFunctionName(obj.constructor));
+ }
+ }
+ }
+
+ return node;
+};
+
+/**
+ * Function: decode
+ *
+ * Decodes the given XML node. The optional "into"
+ * argument specifies an existing object to be
+ * used. If no object is given, then a new instance
+ * is created using the constructor from the codec.
+ *
+ * The function returns the passed in object or
+ * the new instance if no object was given.
+ *
+ * Parameters:
+ *
+ * node - XML node to be decoded.
+ * into - Optional object to be decodec into.
+ */
+mxCodec.prototype.decode = function(node, into)
+{
+ var obj = null;
+
+ if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ var ctor = null;
+
+ try
+ {
+ ctor = eval(node.nodeName);
+ }
+ catch (err)
+ {
+ // ignore
+ }
+
+ try
+ {
+ var dec = mxCodecRegistry.getCodec(ctor);
+
+ if (dec != null)
+ {
+ obj = dec.decode(this, node, into);
+ }
+ else
+ {
+ obj = node.cloneNode(true);
+ obj.removeAttribute('as');
+ }
+ }
+ catch (err)
+ {
+ mxLog.debug('Cannot decode '+node.nodeName+': '+err.message);
+ }
+ }
+
+ return obj;
+};
+
+/**
+ * Function: encodeCell
+ *
+ * Encoding of cell hierarchies is built-into the core, but
+ * is a higher-level function that needs to be explicitely
+ * used by the respective object encoders (eg. <mxModelCodec>,
+ * <mxChildChangeCodec> and <mxRootChangeCodec>). This
+ * implementation writes the given cell and its children as a
+ * (flat) sequence into the given node. The children are not
+ * encoded if the optional includeChildren is false. The
+ * function is in charge of adding the result into the
+ * given node and has no return value.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be encoded.
+ * node - Parent XML node to add the encoded cell into.
+ * includeChildren - Optional boolean indicating if the
+ * function should include all descendents. Default is true.
+ */
+mxCodec.prototype.encodeCell = function(cell, node, includeChildren)
+{
+ node.appendChild(this.encode(cell));
+
+ if (includeChildren == null || includeChildren)
+ {
+ var childCount = cell.getChildCount();
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.encodeCell(cell.getChildAt(i), node);
+ }
+ }
+};
+
+/**
+ * Function: isCellCodec
+ *
+ * Returns true if the given codec is a cell codec. This uses
+ * <mxCellCodec.isCellCodec> to check if the codec is of the
+ * given type.
+ */
+mxCodec.prototype.isCellCodec = function(codec)
+{
+ if (codec != null && typeof(codec.isCellCodec) == 'function')
+ {
+ return codec.isCellCodec();
+ }
+
+ return false;
+};
+
+/**
+ * Function: decodeCell
+ *
+ * Decodes cells that have been encoded using inversion, ie.
+ * where the user object is the enclosing node in the XML,
+ * and restores the group and graph structure in the cells.
+ * Returns a new <mxCell> instance that represents the
+ * given node.
+ *
+ * Parameters:
+ *
+ * node - XML node that contains the cell data.
+ * restoreStructures - Optional boolean indicating whether
+ * the graph structure should be restored by calling insert
+ * and insertEdge on the parent and terminals, respectively.
+ * Default is true.
+ */
+mxCodec.prototype.decodeCell = function(node, restoreStructures)
+{
+ restoreStructures = (restoreStructures != null) ? restoreStructures : true;
+ var cell = null;
+
+ if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Tries to find a codec for the given node name. If that does
+ // not return a codec then the node is the user object (an XML node
+ // that contains the mxCell, aka inversion).
+ var decoder = mxCodecRegistry.getCodec(node.nodeName);
+
+ // Tries to find the codec for the cell inside the user object.
+ // This assumes all node names inside the user object are either
+ // not registered or they correspond to a class for cells.
+ if (!this.isCellCodec(decoder))
+ {
+ var child = node.firstChild;
+
+ while (child != null && !this.isCellCodec(decoder))
+ {
+ decoder = mxCodecRegistry.getCodec(child.nodeName);
+ child = child.nextSibling;
+ }
+ }
+
+ if (!this.isCellCodec(decoder))
+ {
+ decoder = mxCodecRegistry.getCodec(mxCell);
+ }
+
+ cell = decoder.decode(this, node);
+
+ if (restoreStructures)
+ {
+ this.insertIntoGraph(cell);
+ }
+ }
+
+ return cell;
+};
+
+/**
+ * Function: insertIntoGraph
+ *
+ * Inserts the given cell into its parent and terminal cells.
+ */
+mxCodec.prototype.insertIntoGraph = function(cell)
+{
+ var parent = cell.parent;
+ var source = cell.getTerminal(true);
+ var target = cell.getTerminal(false);
+
+ // Fixes possible inconsistencies during insert into graph
+ cell.setTerminal(null, false);
+ cell.setTerminal(null, true);
+ cell.parent = null;
+
+ if (parent != null)
+ {
+ parent.insert(cell);
+ }
+
+ if (source != null)
+ {
+ source.insertEdge(cell, true);
+ }
+
+ if (target != null)
+ {
+ target.insertEdge(cell, false);
+ }
+};
+
+/**
+ * Function: setAttribute
+ *
+ * Sets the attribute on the specified node to value. This is a
+ * helper method that makes sure the attribute and value arguments
+ * are not null.
+ *
+ * Parameters:
+ *
+ * node - XML node to set the attribute for.
+ * attributes - Attributename to be set.
+ * value - New value of the attribute.
+ */
+mxCodec.prototype.setAttribute = function(node, attribute, value)
+{
+ if (attribute != null && value != null)
+ {
+ node.setAttribute(attribute, value);
+ }
+};
diff --git a/src/js/io/mxCodecRegistry.js b/src/js/io/mxCodecRegistry.js
new file mode 100644
index 0000000..a0a0f20
--- /dev/null
+++ b/src/js/io/mxCodecRegistry.js
@@ -0,0 +1,137 @@
+/**
+ * $Id: mxCodecRegistry.js,v 1.12 2010-04-30 13:18:21 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxCodecRegistry =
+{
+ /**
+ * Class: mxCodecRegistry
+ *
+ * Singleton class that acts as a global registry for codecs.
+ *
+ * Adding an <mxCodec>:
+ *
+ * 1. Define a default codec with a new instance of the
+ * object to be handled.
+ *
+ * (code)
+ * var codec = new mxObjectCodec(new mxGraphModel());
+ * (end)
+ *
+ * 2. Define the functions required for encoding and decoding
+ * objects.
+ *
+ * (code)
+ * codec.encode = function(enc, obj) { ... }
+ * codec.decode = function(dec, node, into) { ... }
+ * (end)
+ *
+ * 3. Register the codec in the <mxCodecRegistry>.
+ *
+ * (code)
+ * mxCodecRegistry.register(codec);
+ * (end)
+ *
+ * <mxObjectCodec.decode> may be used to either create a new
+ * instance of an object or to configure an existing instance,
+ * in which case the into argument points to the existing
+ * object. In this case, we say the codec "configures" the
+ * object.
+ *
+ * Variable: codecs
+ *
+ * Maps from constructor names to codecs.
+ */
+ codecs: [],
+
+ /**
+ * Variable: aliases
+ *
+ * Maps from classnames to codecnames.
+ */
+ aliases: [],
+
+ /**
+ * Function: register
+ *
+ * Registers a new codec and associates the name of the template
+ * constructor in the codec with the codec object.
+ *
+ * Parameters:
+ *
+ * codec - <mxObjectCodec> to be registered.
+ */
+ register: function(codec)
+ {
+ if (codec != null)
+ {
+ var name = codec.getName();
+ mxCodecRegistry.codecs[name] = codec;
+
+ var classname = mxUtils.getFunctionName(codec.template.constructor);
+
+ if (classname != name)
+ {
+ mxCodecRegistry.addAlias(classname, name);
+ }
+ }
+
+ return codec;
+ },
+
+ /**
+ * Function: addAlias
+ *
+ * Adds an alias for mapping a classname to a codecname.
+ */
+ addAlias: function(classname, codecname)
+ {
+ mxCodecRegistry.aliases[classname] = codecname;
+ },
+
+ /**
+ * Function: getCodec
+ *
+ * Returns a codec that handles objects that are constructed
+ * using the given constructor.
+ *
+ * Parameters:
+ *
+ * ctor - JavaScript constructor function.
+ */
+ getCodec: function(ctor)
+ {
+ var codec = null;
+
+ if (ctor != null)
+ {
+ var name = mxUtils.getFunctionName(ctor);
+ var tmp = mxCodecRegistry.aliases[name];
+
+ if (tmp != null)
+ {
+ name = tmp;
+ }
+
+ codec = mxCodecRegistry.codecs[name];
+
+ // Registers a new default codec for the given constructor
+ // if no codec has been previously defined.
+ if (codec == null)
+ {
+ try
+ {
+ codec = new mxObjectCodec(new ctor());
+ mxCodecRegistry.register(codec);
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+ }
+
+ return codec;
+ }
+
+};
diff --git a/src/js/io/mxDefaultKeyHandlerCodec.js b/src/js/io/mxDefaultKeyHandlerCodec.js
new file mode 100644
index 0000000..628524a
--- /dev/null
+++ b/src/js/io/mxDefaultKeyHandlerCodec.js
@@ -0,0 +1,88 @@
+/**
+ * $Id: mxDefaultKeyHandlerCodec.js,v 1.5 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxDefaultKeyHandlerCodec
+ *
+ * Custom codec for configuring <mxDefaultKeyHandler>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+ * data for existing key handlers, it does not encode or create key handlers.
+ */
+ var codec = new mxObjectCodec(new mxDefaultKeyHandler());
+
+ /**
+ * Function: encode
+ *
+ * Returns null.
+ */
+ codec.encode = function(enc, obj)
+ {
+ return null;
+ };
+
+ /**
+ * Function: decode
+ *
+ * Reads a sequence of the following child nodes
+ * and attributes:
+ *
+ * Child Nodes:
+ *
+ * add - Binds a keystroke to an actionname.
+ *
+ * Attributes:
+ *
+ * as - Keycode.
+ * action - Actionname to execute in editor.
+ * control - Optional boolean indicating if
+ * the control key must be pressed.
+ *
+ * Example:
+ *
+ * (code)
+ * <mxDefaultKeyHandler as="keyHandler">
+ * <add as="88" control="true" action="cut"/>
+ * <add as="67" control="true" action="copy"/>
+ * <add as="86" control="true" action="paste"/>
+ * </mxDefaultKeyHandler>
+ * (end)
+ *
+ * The keycodes are for the x, c and v keys.
+ *
+ * See also: <mxDefaultKeyHandler.bindAction>,
+ * http://www.js-examples.com/page/tutorials__key_codes.html
+ */
+ codec.decode = function(dec, node, into)
+ {
+ if (into != null)
+ {
+ var editor = into.editor;
+ node = node.firstChild;
+
+ while (node != null)
+ {
+ if (!this.processInclude(dec, node, into) &&
+ node.nodeName == 'add')
+ {
+ var as = node.getAttribute('as');
+ var action = node.getAttribute('action');
+ var control = node.getAttribute('control');
+
+ into.bindAction(as, action, control);
+ }
+
+ node = node.nextSibling;
+ }
+ }
+
+ return into;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxDefaultPopupMenuCodec.js b/src/js/io/mxDefaultPopupMenuCodec.js
new file mode 100644
index 0000000..8d949ea
--- /dev/null
+++ b/src/js/io/mxDefaultPopupMenuCodec.js
@@ -0,0 +1,54 @@
+/**
+ * $Id: mxDefaultPopupMenuCodec.js,v 1.6 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxDefaultPopupMenuCodec
+ *
+ * Custom codec for configuring <mxDefaultPopupMenu>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+ * data for existing popup menus, it does not encode or create menus. Note
+ * that this codec only passes the configuration node to the popup menu,
+ * which uses the config to dynamically create menus. See
+ * <mxDefaultPopupMenu.createMenu>.
+ */
+ var codec = new mxObjectCodec(new mxDefaultPopupMenu());
+
+ /**
+ * Function: encode
+ *
+ * Returns null.
+ */
+ codec.encode = function(enc, obj)
+ {
+ return null;
+ };
+
+ /**
+ * Function: decode
+ *
+ * Uses the given node as the config for <mxDefaultPopupMenu>.
+ */
+ codec.decode = function(dec, node, into)
+ {
+ var inc = node.getElementsByTagName('include')[0];
+
+ if (inc != null)
+ {
+ this.processInclude(dec, inc, into);
+ }
+ else if (into != null)
+ {
+ into.config = node;
+ }
+
+ return into;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxDefaultToolbarCodec.js b/src/js/io/mxDefaultToolbarCodec.js
new file mode 100644
index 0000000..6698b9b
--- /dev/null
+++ b/src/js/io/mxDefaultToolbarCodec.js
@@ -0,0 +1,301 @@
+/**
+ * $Id: mxDefaultToolbarCodec.js,v 1.22 2012-04-11 07:00:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxDefaultToolbarCodec
+ *
+ * Custom codec for configuring <mxDefaultToolbar>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+ * data for existing toolbars handlers, it does not encode or create toolbars.
+ */
+ var codec = new mxObjectCodec(new mxDefaultToolbar());
+
+ /**
+ * Function: encode
+ *
+ * Returns null.
+ */
+ codec.encode = function(enc, obj)
+ {
+ return null;
+ };
+
+ /**
+ * Function: decode
+ *
+ * Reads a sequence of the following child nodes
+ * and attributes:
+ *
+ * Child Nodes:
+ *
+ * add - Adds a new item to the toolbar. See below for attributes.
+ * separator - Adds a vertical separator. No attributes.
+ * hr - Adds a horizontal separator. No attributes.
+ * br - Adds a linefeed. No attributes.
+ *
+ * Attributes:
+ *
+ * as - Resource key for the label.
+ * action - Name of the action to execute in enclosing editor.
+ * mode - Modename (see below).
+ * template - Template name for cell insertion.
+ * style - Optional style to override the template style.
+ * icon - Icon (relative/absolute URL).
+ * pressedIcon - Optional icon for pressed state (relative/absolute URL).
+ * id - Optional ID to be used for the created DOM element.
+ * toggle - Optional 0 or 1 to disable toggling of the element. Default is
+ * 1 (true).
+ *
+ * The action, mode and template attributes are mutually exclusive. The
+ * style can only be used with the template attribute. The add node may
+ * contain another sequence of add nodes with as and action attributes
+ * to create a combo box in the toolbar. If the icon is specified then
+ * a list of the child node is expected to have its template attribute
+ * set and the action is ignored instead.
+ *
+ * Nodes with a specified template may define a function to be used for
+ * inserting the cloned template into the graph. Here is an example of such
+ * a node:
+ *
+ * (code)
+ * <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"><![CDATA[
+ * function (editor, cell, evt, targetCell)
+ * {
+ * var pt = mxUtils.convertPoint(
+ * editor.graph.container, mxEvent.getClientX(evt),
+ * mxEvent.getClientY(evt));
+ * return editor.addVertex(targetCell, cell, pt.x, pt.y);
+ * }
+ * ]]></add>
+ * (end)
+ *
+ * In the above function, editor is the enclosing <mxEditor> instance, cell
+ * is the clone of the template, evt is the mouse event that represents the
+ * drop and targetCell is the cell under the mousepointer where the drop
+ * occurred. The targetCell is retrieved using <mxGraph.getCellAt>.
+ *
+ * Futhermore, nodes with the mode attribute may define a function to
+ * be executed upon selection of the respective toolbar icon. In the
+ * example below, the default edge style is set when this specific
+ * connect-mode is activated:
+ *
+ * (code)
+ * <add as="connect" mode="connect"><![CDATA[
+ * function (editor)
+ * {
+ * if (editor.defaultEdge != null)
+ * {
+ * editor.defaultEdge.style = 'straightEdge';
+ * }
+ * }
+ * ]]></add>
+ * (end)
+ *
+ * Modes:
+ *
+ * select - Left mouse button used for rubberband- & cell-selection.
+ * connect - Allows connecting vertices by inserting new edges.
+ * pan - Disables selection and switches to panning on the left button.
+ *
+ * Example:
+ *
+ * To add items to the toolbar:
+ *
+ * (code)
+ * <mxDefaultToolbar as="toolbar">
+ * <add as="save" action="save" icon="images/save.gif"/>
+ * <br/><hr/>
+ * <add as="select" mode="select" icon="images/select.gif"/>
+ * <add as="connect" mode="connect" icon="images/connect.gif"/>
+ * </mxDefaultToolbar>
+ * (end)
+ */
+ codec.decode = function(dec, node, into)
+ {
+ if (into != null)
+ {
+ var editor = into.editor;
+ node = node.firstChild;
+
+ while (node != null)
+ {
+ if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ if (!this.processInclude(dec, node, into))
+ {
+ if (node.nodeName == 'separator')
+ {
+ into.addSeparator();
+ }
+ else if (node.nodeName == 'br')
+ {
+ into.toolbar.addBreak();
+ }
+ else if (node.nodeName == 'hr')
+ {
+ into.toolbar.addLine();
+ }
+ else if (node.nodeName == 'add')
+ {
+ var as = node.getAttribute('as');
+ as = mxResources.get(as) || as;
+ var icon = node.getAttribute('icon');
+ var pressedIcon = node.getAttribute('pressedIcon');
+ var action = node.getAttribute('action');
+ var mode = node.getAttribute('mode');
+ var template = node.getAttribute('template');
+ var toggle = node.getAttribute('toggle') != '0';
+ var text = mxUtils.getTextContent(node);
+ var elt = null;
+
+ if (action != null)
+ {
+ elt = into.addItem(as, icon, action, pressedIcon);
+ }
+ else if (mode != null)
+ {
+ var funct = mxUtils.eval(text);
+ elt = into.addMode(as, icon, mode, pressedIcon, funct);
+ }
+ else if (template != null || (text != null && text.length > 0))
+ {
+ var cell = editor.templates[template];
+ var style = node.getAttribute('style');
+
+ if (cell != null && style != null)
+ {
+ cell = cell.clone();
+ cell.setStyle(style);
+ }
+
+ var insertFunction = null;
+
+ if (text != null && text.length > 0)
+ {
+ insertFunction = mxUtils.eval(text);
+ }
+
+ elt = into.addPrototype(as, icon, cell, pressedIcon, insertFunction, toggle);
+ }
+ else
+ {
+ var children = mxUtils.getChildNodes(node);
+
+ if (children.length > 0)
+ {
+ if (icon == null)
+ {
+ var combo = into.addActionCombo(as);
+
+ for (var i=0; i<children.length; i++)
+ {
+ var child = children[i];
+
+ if (child.nodeName == 'separator')
+ {
+ into.addOption(combo, '---');
+ }
+ else if (child.nodeName == 'add')
+ {
+ var lab = child.getAttribute('as');
+ var act = child.getAttribute('action');
+ into.addActionOption(combo, lab, act);
+ }
+ }
+ }
+ else
+ {
+ var select = null;
+ var create = function()
+ {
+ var template = editor.templates[select.value];
+
+ if (template != null)
+ {
+ var clone = template.clone();
+ var style = select.options[select.selectedIndex].cellStyle;
+
+ if (style != null)
+ {
+ clone.setStyle(style);
+ }
+
+ return clone;
+ }
+ else
+ {
+ mxLog.warn('Template '+template+' not found');
+ }
+
+ return null;
+ };
+
+ var img = into.addPrototype(as, icon, create, null, null, toggle);
+ select = into.addCombo();
+
+ // Selects the toolbar icon if a selection change
+ // is made in the corresponding combobox.
+ mxEvent.addListener(select, 'change', function()
+ {
+ into.toolbar.selectMode(img, function(evt)
+ {
+ var pt = mxUtils.convertPoint(editor.graph.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ return editor.addVertex(null, funct(), pt.x, pt.y);
+ });
+
+ into.toolbar.noReset = false;
+ });
+
+ // Adds the entries to the combobox
+ for (var i=0; i<children.length; i++)
+ {
+ var child = children[i];
+
+ if (child.nodeName == 'separator')
+ {
+ into.addOption(select, '---');
+ }
+ else if (child.nodeName == 'add')
+ {
+ var lab = child.getAttribute('as');
+ var tmp = child.getAttribute('template');
+ var option = into.addOption(select, lab, tmp || template);
+ option.cellStyle = child.getAttribute('style');
+ }
+ }
+
+ }
+ }
+ }
+
+ // Assigns an ID to the created element to access it later.
+ if (elt != null)
+ {
+ var id = node.getAttribute('id');
+
+ if (id != null && id.length > 0)
+ {
+ elt.setAttribute('id', id);
+ }
+ }
+ }
+ }
+ }
+
+ node = node.nextSibling;
+ }
+ }
+
+ return into;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxEditorCodec.js b/src/js/io/mxEditorCodec.js
new file mode 100644
index 0000000..f61bd95
--- /dev/null
+++ b/src/js/io/mxEditorCodec.js
@@ -0,0 +1,246 @@
+/**
+ * $Id: mxEditorCodec.js,v 1.11 2010-01-04 11:18:26 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxEditorCodec
+ *
+ * Codec for <mxEditor>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - modified
+ * - lastSnapshot
+ * - ignoredChanges
+ * - undoManager
+ * - graphContainer
+ * - toolbarContainer
+ */
+ var codec = new mxObjectCodec(new mxEditor(),
+ ['modified', 'lastSnapshot', 'ignoredChanges',
+ 'undoManager', 'graphContainer', 'toolbarContainer']);
+
+ /**
+ * Function: beforeDecode
+ *
+ * Decodes the ui-part of the configuration node by reading
+ * a sequence of the following child nodes and attributes
+ * and passes the control to the default decoding mechanism:
+ *
+ * Child Nodes:
+ *
+ * stylesheet - Adds a CSS stylesheet to the document.
+ * resource - Adds the basename of a resource bundle.
+ * add - Creates or configures a known UI element.
+ *
+ * These elements may appear in any order given that the
+ * graph UI element is added before the toolbar element
+ * (see Known Keys).
+ *
+ * Attributes:
+ *
+ * as - Key for the UI element (see below).
+ * element - ID for the element in the document.
+ * style - CSS style to be used for the element or window.
+ * x - X coordinate for the new window.
+ * y - Y coordinate for the new window.
+ * width - Width for the new window.
+ * height - Optional height for the new window.
+ * name - Name of the stylesheet (absolute/relative URL).
+ * basename - Basename of the resource bundle (see <mxResources>).
+ *
+ * The x, y, width and height attributes are used to create a new
+ * <mxWindow> if the element attribute is not specified in an add
+ * node. The name and basename are only used in the stylesheet and
+ * resource nodes, respectively.
+ *
+ * Known Keys:
+ *
+ * graph - Main graph element (see <mxEditor.setGraphContainer>).
+ * title - Title element (see <mxEditor.setTitleContainer>).
+ * toolbar - Toolbar element (see <mxEditor.setToolbarContainer>).
+ * status - Status bar element (see <mxEditor.setStatusContainer>).
+ *
+ * Example:
+ *
+ * (code)
+ * <ui>
+ * <stylesheet name="css/process.css"/>
+ * <resource basename="resources/mxApplication"/>
+ * <add as="graph" element="graph"
+ * style="left:70px;right:20px;top:20px;bottom:40px"/>
+ * <add as="status" element="status"/>
+ * <add as="toolbar" x="10" y="20" width="54"/>
+ * </ui>
+ * (end)
+ */
+ codec.afterDecode = function(dec, node, obj)
+ {
+ // Assigns the specified templates for edges
+ var defaultEdge = node.getAttribute('defaultEdge');
+
+ if (defaultEdge != null)
+ {
+ node.removeAttribute('defaultEdge');
+ obj.defaultEdge = obj.templates[defaultEdge];
+ }
+
+ // Assigns the specified templates for groups
+ var defaultGroup = node.getAttribute('defaultGroup');
+
+ if (defaultGroup != null)
+ {
+ node.removeAttribute('defaultGroup');
+ obj.defaultGroup = obj.templates[defaultGroup];
+ }
+
+ return obj;
+ };
+
+ /**
+ * Function: decodeChild
+ *
+ * Overrides decode child to handle special child nodes.
+ */
+ codec.decodeChild = function(dec, child, obj)
+ {
+ if (child.nodeName == 'Array')
+ {
+ var role = child.getAttribute('as');
+
+ if (role == 'templates')
+ {
+ this.decodeTemplates(dec, child, obj);
+ return;
+ }
+ }
+ else if (child.nodeName == 'ui')
+ {
+ this.decodeUi(dec, child, obj);
+ return;
+ }
+
+ mxObjectCodec.prototype.decodeChild.apply(this, arguments);
+ };
+
+ /**
+ * Function: decodeTemplates
+ *
+ * Decodes the cells from the given node as templates.
+ */
+ codec.decodeUi = function(dec, node, editor)
+ {
+ var tmp = node.firstChild;
+ while (tmp != null)
+ {
+ if (tmp.nodeName == 'add')
+ {
+ var as = tmp.getAttribute('as');
+ var elt = tmp.getAttribute('element');
+ var style = tmp.getAttribute('style');
+ var element = null;
+
+ if (elt != null)
+ {
+ element = document.getElementById(elt);
+
+ if (element != null &&
+ style != null)
+ {
+ element.style.cssText += ';'+style;
+ }
+ }
+ else
+ {
+ var x = parseInt(tmp.getAttribute('x'));
+ var y = parseInt(tmp.getAttribute('y'));
+ var width = tmp.getAttribute('width');
+ var height = tmp.getAttribute('height');
+
+ // Creates a new window around the element
+ element = document.createElement('div');
+ element.style.cssText = style;
+
+ var wnd = new mxWindow(mxResources.get(as) || as,
+ element, x, y, width, height, false, true);
+ wnd.setVisible(true);
+ }
+
+ // TODO: Make more generic
+ if (as == 'graph')
+ {
+ editor.setGraphContainer(element);
+ }
+ else if (as == 'toolbar')
+ {
+ editor.setToolbarContainer(element);
+ }
+ else if (as == 'title')
+ {
+ editor.setTitleContainer(element);
+ }
+ else if (as == 'status')
+ {
+ editor.setStatusContainer(element);
+ }
+ else if (as == 'map')
+ {
+ editor.setMapContainer(element);
+ }
+ }
+ else if (tmp.nodeName == 'resource')
+ {
+ mxResources.add(tmp.getAttribute('basename'));
+ }
+ else if (tmp.nodeName == 'stylesheet')
+ {
+ mxClient.link('stylesheet', tmp.getAttribute('name'));
+ }
+
+ tmp = tmp.nextSibling;
+ }
+ };
+
+ /**
+ * Function: decodeTemplates
+ *
+ * Decodes the cells from the given node as templates.
+ */
+ codec.decodeTemplates = function(dec, node, editor)
+ {
+ if (editor.templates == null)
+ {
+ editor.templates = [];
+ }
+
+ var children = mxUtils.getChildNodes(node);
+ for (var j=0; j<children.length; j++)
+ {
+ var name = children[j].getAttribute('as');
+ var child = children[j].firstChild;
+
+ while (child != null && child.nodeType != 1)
+ {
+ child = child.nextSibling;
+ }
+
+ if (child != null)
+ {
+ // LATER: Only single cells means you need
+ // to group multiple cells within another
+ // cell. This should be changed to support
+ // arrays of cells, or the wrapper must
+ // be automatically handled in this class.
+ editor.templates[name] = dec.decodeCell(child);
+ }
+ }
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxGenericChangeCodec.js b/src/js/io/mxGenericChangeCodec.js
new file mode 100644
index 0000000..8da7789
--- /dev/null
+++ b/src/js/io/mxGenericChangeCodec.js
@@ -0,0 +1,64 @@
+/**
+ * $Id: mxGenericChangeCodec.js,v 1.11 2010-09-13 15:50:36 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGenericChangeCodec
+ *
+ * Codec for <mxValueChange>s, <mxStyleChange>s, <mxGeometryChange>s,
+ * <mxCollapseChange>s and <mxVisibleChange>s. This class is created
+ * and registered dynamically at load time and used implicitely
+ * via <mxCodec> and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ *
+ * Reference Fields:
+ *
+ * - cell
+ *
+ * Constructor: mxGenericChangeCodec
+ *
+ * Factory function that creates a <mxObjectCodec> for
+ * the specified change and fieldname.
+ *
+ * Parameters:
+ *
+ * obj - An instance of the change object.
+ * variable - The fieldname for the change data.
+ */
+var mxGenericChangeCodec = function(obj, variable)
+{
+ var codec = new mxObjectCodec(obj, ['model', 'previous'], ['cell']);
+
+ /**
+ * Function: afterDecode
+ *
+ * Restores the state by assigning the previous value.
+ */
+ codec.afterDecode = function(dec, node, obj)
+ {
+ // Allows forward references in sessions. This is a workaround
+ // for the sequence of edits in mxGraph.moveCells and cellsAdded.
+ if (mxUtils.isNode(obj.cell))
+ {
+ obj.cell = dec.decodeCell(obj.cell, false);
+ }
+
+ obj.previous = obj[variable];
+
+ return obj;
+ };
+
+ return codec;
+};
+
+// Registers the codecs
+mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange(), 'value'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange(), 'style'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange(), 'geometry'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange(), 'collapsed'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange(), 'visible'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange(), 'value'));
diff --git a/src/js/io/mxGraphCodec.js b/src/js/io/mxGraphCodec.js
new file mode 100644
index 0000000..f052e13
--- /dev/null
+++ b/src/js/io/mxGraphCodec.js
@@ -0,0 +1,28 @@
+/**
+ * $Id: mxGraphCodec.js,v 1.8 2010-06-10 06:54:18 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxGraphCodec
+ *
+ * Codec for <mxGraph>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - graphListeners
+ * - eventListeners
+ * - view
+ * - container
+ * - cellRenderer
+ * - editor
+ * - selection
+ */
+ return new mxObjectCodec(new mxGraph(),
+ ['graphListeners', 'eventListeners', 'view', 'container',
+ 'cellRenderer', 'editor', 'selection']);
+
+}());
diff --git a/src/js/io/mxGraphViewCodec.js b/src/js/io/mxGraphViewCodec.js
new file mode 100644
index 0000000..110b212
--- /dev/null
+++ b/src/js/io/mxGraphViewCodec.js
@@ -0,0 +1,197 @@
+/**
+ * $Id: mxGraphViewCodec.js,v 1.18 2010-12-03 11:05:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxGraphViewCodec
+ *
+ * Custom encoder for <mxGraphView>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only writes views
+ * into a XML format that can be used to create an image for
+ * the graph, that is, it contains absolute coordinates with
+ * computed perimeters, edge styles and cell styles.
+ */
+ var codec = new mxObjectCodec(new mxGraphView());
+
+ /**
+ * Function: encode
+ *
+ * Encodes the given <mxGraphView> using <encodeCell>
+ * starting at the model's root. This returns the
+ * top-level graph node of the recursive encoding.
+ */
+ codec.encode = function(enc, view)
+ {
+ return this.encodeCell(enc, view,
+ view.graph.getModel().getRoot());
+ };
+
+ /**
+ * Function: encodeCell
+ *
+ * Recursively encodes the specifed cell. Uses layer
+ * as the default nodename. If the cell's parent is
+ * null, then graph is used for the nodename. If
+ * <mxGraphModel.isEdge> returns true for the cell,
+ * then edge is used for the nodename, else if
+ * <mxGraphModel.isVertex> returns true for the cell,
+ * then vertex is used for the nodename.
+ *
+ * <mxGraph.getLabel> is used to create the label
+ * attribute for the cell. For graph nodes and vertices
+ * the bounds are encoded into x, y, width and height.
+ * For edges the points are encoded into a points
+ * attribute as a space-separated list of comma-separated
+ * coordinate pairs (eg. x0,y0 x1,y1 ... xn,yn). All
+ * values from the cell style are added as attribute
+ * values to the node.
+ */
+ codec.encodeCell = function(enc, view, cell)
+ {
+ var model = view.graph.getModel();
+ var state = view.getState(cell);
+ var parent = model.getParent(cell);
+
+ if (parent == null || state != null)
+ {
+ var childCount = model.getChildCount(cell);
+ var geo = view.graph.getCellGeometry(cell);
+ var name = null;
+
+ if (parent == model.getRoot())
+ {
+ name = 'layer';
+ }
+ else if (parent == null)
+ {
+ name = 'graph';
+ }
+ else if (model.isEdge(cell))
+ {
+ name = 'edge';
+ }
+ else if (childCount > 0 && geo != null)
+ {
+ name = 'group';
+ }
+ else if (model.isVertex(cell))
+ {
+ name = 'vertex';
+ }
+
+ if (name != null)
+ {
+ var node = enc.document.createElement(name);
+ var lab = view.graph.getLabel(cell);
+
+ if (lab != null)
+ {
+ node.setAttribute('label', view.graph.getLabel(cell));
+
+ if (view.graph.isHtmlLabel(cell))
+ {
+ node.setAttribute('html', true);
+ }
+ }
+
+ if (parent == null)
+ {
+ var bounds = view.getGraphBounds();
+
+ if (bounds != null)
+ {
+ node.setAttribute('x', Math.round(bounds.x));
+ node.setAttribute('y', Math.round(bounds.y));
+ node.setAttribute('width', Math.round(bounds.width));
+ node.setAttribute('height', Math.round(bounds.height));
+ }
+
+ node.setAttribute('scale', view.scale);
+ }
+ else if (state != null && geo != null)
+ {
+ // Writes each key, value in the style pair to an attribute
+ for (var i in state.style)
+ {
+ var value = state.style[i];
+
+ // Tries to turn objects and functions into strings
+ if (typeof(value) == 'function' &&
+ typeof(value) == 'object')
+ {
+ value = mxStyleRegistry.getName(value);
+ }
+
+ if (value != null &&
+ typeof(value) != 'function' &&
+ typeof(value) != 'object')
+ {
+ node.setAttribute(i, value);
+ }
+ }
+
+ var abs = state.absolutePoints;
+
+ // Writes the list of points into one attribute
+ if (abs != null && abs.length > 0)
+ {
+ var pts = Math.round(abs[0].x) + ',' + Math.round(abs[0].y);
+
+ for (var i=1; i<abs.length; i++)
+ {
+ pts += ' ' + Math.round(abs[i].x) + ',' +
+ Math.round(abs[i].y);
+ }
+
+ node.setAttribute('points', pts);
+ }
+
+ // Writes the bounds into 4 attributes
+ else
+ {
+ node.setAttribute('x', Math.round(state.x));
+ node.setAttribute('y', Math.round(state.y));
+ node.setAttribute('width', Math.round(state.width));
+ node.setAttribute('height', Math.round(state.height));
+ }
+
+ var offset = state.absoluteOffset;
+
+ // Writes the offset into 2 attributes
+ if (offset != null)
+ {
+ if (offset.x != 0)
+ {
+ node.setAttribute('dx', Math.round(offset.x));
+ }
+
+ if (offset.y != 0)
+ {
+ node.setAttribute('dy', Math.round(offset.y));
+ }
+ }
+ }
+
+ for (var i=0; i<childCount; i++)
+ {
+ var childNode = this.encodeCell(enc,
+ view, model.getChildAt(cell, i));
+
+ if (childNode != null)
+ {
+ node.appendChild(childNode);
+ }
+ }
+ }
+ }
+
+ return node;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxModelCodec.js b/src/js/io/mxModelCodec.js
new file mode 100644
index 0000000..760a2b1
--- /dev/null
+++ b/src/js/io/mxModelCodec.js
@@ -0,0 +1,80 @@
+/**
+ * $Id: mxModelCodec.js,v 1.11 2010-11-23 08:46:41 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxModelCodec
+ *
+ * Codec for <mxGraphModel>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ */
+ var codec = new mxObjectCodec(new mxGraphModel());
+
+ /**
+ * Function: encodeObject
+ *
+ * Encodes the given <mxGraphModel> by writing a (flat) XML sequence of
+ * cell nodes as produced by the <mxCellCodec>. The sequence is
+ * wrapped-up in a node with the name root.
+ */
+ codec.encodeObject = function(enc, obj, node)
+ {
+ var rootNode = enc.document.createElement('root');
+ enc.encodeCell(obj.getRoot(), rootNode);
+ node.appendChild(rootNode);
+ };
+
+ /**
+ * Function: decodeChild
+ *
+ * Overrides decode child to handle special child nodes.
+ */
+ codec.decodeChild = function(dec, child, obj)
+ {
+ if (child.nodeName == 'root')
+ {
+ this.decodeRoot(dec, child, obj);
+ }
+ else
+ {
+ mxObjectCodec.prototype.decodeChild.apply(this, arguments);
+ }
+ };
+
+ /**
+ * Function: decodeRoot
+ *
+ * Reads the cells into the graph model. All cells
+ * are children of the root element in the node.
+ */
+ codec.decodeRoot = function(dec, root, model)
+ {
+ var rootCell = null;
+ var tmp = root.firstChild;
+
+ while (tmp != null)
+ {
+ var cell = dec.decodeCell(tmp);
+
+ if (cell != null && cell.getParent() == null)
+ {
+ rootCell = cell;
+ }
+
+ tmp = tmp.nextSibling;
+ }
+
+ // Sets the root on the model if one has been decoded
+ if (rootCell != null)
+ {
+ model.setRoot(rootCell);
+ }
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxObjectCodec.js b/src/js/io/mxObjectCodec.js
new file mode 100644
index 0000000..9d2473a
--- /dev/null
+++ b/src/js/io/mxObjectCodec.js
@@ -0,0 +1,983 @@
+/**
+ * $Id: mxObjectCodec.js,v 1.49 2010-12-01 09:19:58 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxObjectCodec
+ *
+ * Generic codec for JavaScript objects that implements a mapping between
+ * JavaScript objects and XML nodes that maps each field or element to an
+ * attribute or child node, and vice versa.
+ *
+ * Atomic Values:
+ *
+ * Consider the following example.
+ *
+ * (code)
+ * var obj = new Object();
+ * obj.foo = "Foo";
+ * obj.bar = "Bar";
+ * (end)
+ *
+ * This object is encoded into an XML node using the following.
+ *
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(obj);
+ * (end)
+ *
+ * The output of the encoding may be viewed using <mxLog> as follows.
+ *
+ * (code)
+ * mxLog.show();
+ * mxLog.debug(mxUtils.getPrettyXml(node));
+ * (end)
+ *
+ * Finally, the result of the encoding looks as follows.
+ *
+ * (code)
+ * <Object foo="Foo" bar="Bar"/>
+ * (end)
+ *
+ * In the above output, the foo and bar fields have been mapped to attributes
+ * with the same names, and the name of the constructor was used for the
+ * nodename.
+ *
+ * Booleans:
+ *
+ * Since booleans are numbers in JavaScript, all boolean values are encoded
+ * into 1 for true and 0 for false. The decoder also accepts the string true
+ * and false for boolean values.
+ *
+ * Objects:
+ *
+ * The above scheme is applied to all atomic fields, that is, to all non-object
+ * fields of an object. For object fields, a child node is created with a
+ * special attribute that contains the fieldname. This special attribute is
+ * called "as" and hence, as is a reserved word that should not be used for a
+ * fieldname.
+ *
+ * Consider the following example where foo is an object and bar is an atomic
+ * property of foo.
+ *
+ * (code)
+ * var obj = {foo: {bar: "Bar"}};
+ * (end)
+ *
+ * This will be mapped to the following XML structure by mxObjectCodec.
+ *
+ * (code)
+ * <Object>
+ * <Object bar="Bar" as="foo"/>
+ * </Object>
+ * (end)
+ *
+ * In the above output, the inner Object node contains the as-attribute that
+ * specifies the fieldname in the enclosing object. That is, the field foo was
+ * mapped to a child node with an as-attribute that has the value foo.
+ *
+ * Arrays:
+ *
+ * Arrays are special objects that are either associative, in which case each
+ * key, value pair is treated like a field where the key is the fieldname, or
+ * they are a sequence of atomic values and objects, which is mapped to a
+ * sequence of child nodes. For object elements, the above scheme is applied
+ * without the use of the special as-attribute for creating each child. For
+ * atomic elements, a special add-node is created with the value stored in the
+ * value-attribute.
+ *
+ * For example, the following array contains one atomic value and one object
+ * with a field called bar. Furthermore it contains two associative entries
+ * called bar with an atomic value, and foo with an object value.
+ *
+ * (code)
+ * var obj = ["Bar", {bar: "Bar"}];
+ * obj["bar"] = "Bar";
+ * obj["foo"] = {bar: "Bar"};
+ * (end)
+ *
+ * This array is represented by the following XML nodes.
+ *
+ * (code)
+ * <Array bar="Bar">
+ * <add value="Bar"/>
+ * <Object bar="Bar"/>
+ * <Object bar="Bar" as="foo"/>
+ * </Array>
+ * (end)
+ *
+ * The Array node name is the name of the constructor. The additional
+ * as-attribute in the last child contains the key of the associative entry,
+ * whereas the second last child is part of the array sequence and does not
+ * have an as-attribute.
+ *
+ * References:
+ *
+ * Objects may be represented as child nodes or attributes with ID values,
+ * which are used to lookup the object in a table within <mxCodec>. The
+ * <isReference> function is in charge of deciding if a specific field should
+ * be encoded as a reference or not. Its default implementation returns true if
+ * the fieldname is in <idrefs>, an array of strings that is used to configure
+ * the <mxObjectCodec>.
+ *
+ * Using this approach, the mapping does not guarantee that the referenced
+ * object itself exists in the document. The fields that are encoded as
+ * references must be carefully chosen to make sure all referenced objects
+ * exist in the document, or may be resolved by some other means if necessary.
+ *
+ * For example, in the case of the graph model all cells are stored in a tree
+ * whose root is referenced by the model's root field. A tree is a structure
+ * that is well suited for an XML representation, however, the additional edges
+ * in the graph model have a reference to a source and target cell, which are
+ * also contained in the tree. To handle this case, the source and target cell
+ * of an edge are treated as references, whereas the children are treated as
+ * objects. Since all cells are contained in the tree and no edge references a
+ * source or target outside the tree, this setup makes sure all referenced
+ * objects are contained in the document.
+ *
+ * In the case of a tree structure we must further avoid infinite recursion by
+ * ignoring the parent reference of each child. This is done by returning true
+ * in <isExcluded>, whose default implementation uses the array of excluded
+ * fieldnames passed to the mxObjectCodec constructor.
+ *
+ * References are only used for cells in mxGraph. For defining other
+ * referencable object types, the codec must be able to work out the ID of an
+ * object. This is done by implementing <mxCodec.reference>. For decoding a
+ * reference, the XML node with the respective id-attribute is fetched from the
+ * document, decoded, and stored in a lookup table for later reference. For
+ * looking up external objects, <mxCodec.lookup> may be implemented.
+ *
+ * Expressions:
+ *
+ * For decoding JavaScript expressions, the add-node may be used with a text
+ * content that contains the JavaScript expression. For example, the following
+ * creates a field called foo in the enclosing object and assigns it the value
+ * of <mxConstants.ALIGN_LEFT>.
+ *
+ * (code)
+ * <Object>
+ * <add as="foo">mxConstants.ALIGN_LEFT</add>
+ * </Object>
+ * (end)
+ *
+ * The resulting object has a field called foo with the value "left". Its XML
+ * representation looks as follows.
+ *
+ * (code)
+ * <Object foo="left"/>
+ * (end)
+ *
+ * This means the expression is evaluated at decoding time and the result of
+ * the evaluation is stored in the respective field. Valid expressions are all
+ * JavaScript expressions, including function definitions, which are mapped to
+ * functions on the resulting object.
+ *
+ * Constructor: mxObjectCodec
+ *
+ * Constructs a new codec for the specified template object.
+ * The variables in the optional exclude array are ignored by
+ * the codec. Variables in the optional idrefs array are
+ * turned into references in the XML. The optional mapping
+ * may be used to map from variable names to XML attributes.
+ * The argument is created as follows:
+ *
+ * (code)
+ * var mapping = new Object();
+ * mapping['variableName'] = 'attribute-name';
+ * (end)
+ *
+ * Parameters:
+ *
+ * template - Prototypical instance of the object to be
+ * encoded/decoded.
+ * exclude - Optional array of fieldnames to be ignored.
+ * idrefs - Optional array of fieldnames to be converted to/from
+ * references.
+ * mapping - Optional mapping from field- to attributenames.
+ */
+function mxObjectCodec(template, exclude, idrefs, mapping)
+{
+ this.template = template;
+
+ this.exclude = (exclude != null) ? exclude : [];
+ this.idrefs = (idrefs != null) ? idrefs : [];
+ this.mapping = (mapping != null) ? mapping : [];
+
+ this.reverse = new Object();
+
+ for (var i in this.mapping)
+ {
+ this.reverse[this.mapping[i]] = i;
+ }
+};
+
+/**
+ * Variable: template
+ *
+ * Holds the template object associated with this codec.
+ */
+mxObjectCodec.prototype.template = null;
+
+/**
+ * Variable: exclude
+ *
+ * Array containing the variable names that should be
+ * ignored by the codec.
+ */
+mxObjectCodec.prototype.exclude = null;
+
+/**
+ * Variable: idrefs
+ *
+ * Array containing the variable names that should be
+ * turned into or converted from references. See
+ * <mxCodec.getId> and <mxCodec.getObject>.
+ */
+mxObjectCodec.prototype.idrefs = null;
+
+/**
+ * Variable: mapping
+ *
+ * Maps from from fieldnames to XML attribute names.
+ */
+mxObjectCodec.prototype.mapping = null;
+
+/**
+ * Variable: reverse
+ *
+ * Maps from from XML attribute names to fieldnames.
+ */
+mxObjectCodec.prototype.reverse = null;
+
+/**
+ * Function: getName
+ *
+ * Returns the name used for the nodenames and lookup of the codec when
+ * classes are encoded and nodes are decoded. For classes to work with
+ * this the codec registry automatically adds an alias for the classname
+ * if that is different than what this returns. The default implementation
+ * returns the classname of the template class.
+ */
+mxObjectCodec.prototype.getName = function()
+{
+ return mxUtils.getFunctionName(this.template.constructor);
+};
+
+/**
+ * Function: cloneTemplate
+ *
+ * Returns a new instance of the template for this codec.
+ */
+mxObjectCodec.prototype.cloneTemplate = function()
+{
+ return new this.template.constructor();
+};
+
+/**
+ * Function: getFieldName
+ *
+ * Returns the fieldname for the given attributename.
+ * Looks up the value in the <reverse> mapping or returns
+ * the input if there is no reverse mapping for the
+ * given name.
+ */
+mxObjectCodec.prototype.getFieldName = function(attributename)
+{
+ if (attributename != null)
+ {
+ var mapped = this.reverse[attributename];
+
+ if (mapped != null)
+ {
+ attributename = mapped;
+ }
+ }
+
+ return attributename;
+};
+
+/**
+ * Function: getAttributeName
+ *
+ * Returns the attributename for the given fieldname.
+ * Looks up the value in the <mapping> or returns
+ * the input if there is no mapping for the
+ * given name.
+ */
+mxObjectCodec.prototype.getAttributeName = function(fieldname)
+{
+ if (fieldname != null)
+ {
+ var mapped = this.mapping[fieldname];
+
+ if (mapped != null)
+ {
+ fieldname = mapped;
+ }
+ }
+
+ return fieldname;
+};
+
+/**
+ * Function: isExcluded
+ *
+ * Returns true if the given attribute is to be ignored by the codec. This
+ * implementation returns true if the given fieldname is in <exclude> or
+ * if the fieldname equals <mxObjectIdentity.FIELD_NAME>.
+ *
+ * Parameters:
+ *
+ * obj - Object instance that contains the field.
+ * attr - Fieldname of the field.
+ * value - Value of the field.
+ * write - Boolean indicating if the field is being encoded or decoded.
+ * Write is true if the field is being encoded, else it is being decoded.
+ */
+mxObjectCodec.prototype.isExcluded = function(obj, attr, value, write)
+{
+ return attr == mxObjectIdentity.FIELD_NAME ||
+ mxUtils.indexOf(this.exclude, attr) >= 0;
+};
+
+/**
+ * Function: isReference
+ *
+ * Returns true if the given fieldname is to be treated
+ * as a textual reference (ID). This implementation returns
+ * true if the given fieldname is in <idrefs>.
+ *
+ * Parameters:
+ *
+ * obj - Object instance that contains the field.
+ * attr - Fieldname of the field.
+ * value - Value of the field.
+ * write - Boolean indicating if the field is being encoded or decoded.
+ * Write is true if the field is being encoded, else it is being decoded.
+ */
+mxObjectCodec.prototype.isReference = function(obj, attr, value, write)
+{
+ return mxUtils.indexOf(this.idrefs, attr) >= 0;
+};
+
+/**
+ * Function: encode
+ *
+ * Encodes the specified object and returns a node
+ * representing then given object. Calls <beforeEncode>
+ * after creating the node and <afterEncode> with the
+ * resulting node after processing.
+ *
+ * Enc is a reference to the calling encoder. It is used
+ * to encode complex objects and create references.
+ *
+ * This implementation encodes all variables of an
+ * object according to the following rules:
+ *
+ * - If the variable name is in <exclude> then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getId>
+ * is used to replace the object with its ID.
+ * - The variable name is mapped using <mapping>.
+ * - If obj is an array and the variable name is numeric
+ * (ie. an index) then it is not encoded.
+ * - If the value is an object, then the codec is used to
+ * create a child node with the variable name encoded into
+ * the "as" attribute.
+ * - Else, if <encodeDefaults> is true or the value differs
+ * from the template value, then ...
+ * - ... if obj is not an array, then the value is mapped to
+ * an attribute.
+ * - ... else if obj is an array, the value is mapped to an
+ * add child with a value attribute or a text child node,
+ * if the value is a function.
+ *
+ * If no ID exists for a variable in <idrefs> or if an object
+ * cannot be encoded, a warning is issued using <mxLog.warn>.
+ *
+ * Returns the resulting XML node that represents the given
+ * object.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ */
+mxObjectCodec.prototype.encode = function(enc, obj)
+{
+ var node = enc.document.createElement(this.getName());
+
+ obj = this.beforeEncode(enc, obj, node);
+ this.encodeObject(enc, obj, node);
+
+ return this.afterEncode(enc, obj, node);
+};
+
+/**
+ * Function: encodeObject
+ *
+ * Encodes the value of each member in then given obj into the given node using
+ * <encodeValue>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node that contains the encoded object.
+ */
+mxObjectCodec.prototype.encodeObject = function(enc, obj, node)
+{
+ enc.setAttribute(node, 'id', enc.getId(obj));
+
+ for (var i in obj)
+ {
+ var name = i;
+ var value = obj[name];
+
+ if (value != null && !this.isExcluded(obj, name, value, true))
+ {
+ if (mxUtils.isNumeric(name))
+ {
+ name = null;
+ }
+
+ this.encodeValue(enc, obj, name, value, node);
+ }
+ }
+};
+
+/**
+ * Function: encodeValue
+ *
+ * Converts the given value according to the mappings
+ * and id-refs in this codec and uses <writeAttribute>
+ * to write the attribute into the given node.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object whose property is going to be encoded.
+ * name - XML node that contains the encoded object.
+ * value - Value of the property to be encoded.
+ * node - XML node that contains the encoded object.
+ */
+mxObjectCodec.prototype.encodeValue = function(enc, obj,
+ name, value, node)
+{
+ if (value != null)
+ {
+ if (this.isReference(obj, name, value, true))
+ {
+ var tmp = enc.getId(value);
+
+ if (tmp == null)
+ {
+ mxLog.warn('mxObjectCodec.encode: No ID for ' +
+ this.getName() + '.' + name + '=' + value);
+ return; // exit
+ }
+
+ value = tmp;
+ }
+
+ var defaultValue = this.template[name];
+
+ // Checks if the value is a default value and
+ // the name is correct
+ if (name == null || enc.encodeDefaults ||
+ defaultValue != value)
+ {
+ name = this.getAttributeName(name);
+ this.writeAttribute(enc, obj, name, value, node);
+ }
+ }
+};
+
+/**
+ * Function: writeAttribute
+ *
+ * Writes the given value into node using <writePrimitiveAttribute>
+ * or <writeComplexAttribute> depending on the type of the value.
+ */
+mxObjectCodec.prototype.writeAttribute = function(enc, obj,
+ attr, value, node)
+{
+ if (typeof(value) != 'object' /* primitive type */)
+ {
+ this.writePrimitiveAttribute(enc, obj, attr, value, node);
+ }
+ else /* complex type */
+ {
+ this.writeComplexAttribute(enc, obj, attr, value, node);
+ }
+};
+
+/**
+ * Function: writePrimitiveAttribute
+ *
+ * Writes the given value as an attribute of the given node.
+ */
+mxObjectCodec.prototype.writePrimitiveAttribute = function(enc, obj,
+ attr, value, node)
+{
+ value = this.convertValueToXml(value);
+
+ if (attr == null)
+ {
+ var child = enc.document.createElement('add');
+
+ if (typeof(value) == 'function')
+ {
+ child.appendChild(
+ enc.document.createTextNode(value));
+ }
+ else
+ {
+ enc.setAttribute(child, 'value', value);
+ }
+
+ node.appendChild(child);
+ }
+ else if (typeof(value) != 'function')
+ {
+ enc.setAttribute(node, attr, value);
+ }
+};
+
+/**
+ * Function: writeComplexAttribute
+ *
+ * Writes the given value as a child node of the given node.
+ */
+mxObjectCodec.prototype.writeComplexAttribute = function(enc, obj,
+ attr, value, node)
+{
+ var child = enc.encode(value);
+
+ if (child != null)
+ {
+ if (attr != null)
+ {
+ child.setAttribute('as', attr);
+ }
+
+ node.appendChild(child);
+ }
+ else
+ {
+ mxLog.warn('mxObjectCodec.encode: No node for ' +
+ this.getName() + '.' + attr + ': ' + value);
+ }
+};
+
+/**
+ * Function: convertValueToXml
+ *
+ * Converts true to "1" and false to "0". All other values are ignored.
+ */
+mxObjectCodec.prototype.convertValueToXml = function(value)
+{
+ // Makes sure to encode boolean values as numeric values
+ if (typeof(value.length) == 'undefined' &&
+ (value == true ||
+ value == false))
+ {
+ // Checks if the value is true (do not use the
+ // value as is, because this would check if the
+ // value is not null, so 0 would be true!
+ value = (value == true) ? '1' : '0';
+ }
+ return value;
+};
+
+/**
+ * Function: convertValueFromXml
+ *
+ * Converts booleans and numeric values to the respective types.
+ */
+mxObjectCodec.prototype.convertValueFromXml = function(value)
+{
+ if (mxUtils.isNumeric(value))
+ {
+ value = parseFloat(value);
+ }
+
+ return value;
+};
+
+/**
+ * Function: beforeEncode
+ *
+ * Hook for subclassers to pre-process the object before
+ * encoding. This returns the input object. The return
+ * value of this function is used in <encode> to perform
+ * the default encoding into the given node.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node to encode the object into.
+ */
+mxObjectCodec.prototype.beforeEncode = function(enc, obj, node)
+{
+ return obj;
+};
+
+/**
+ * Function: afterEncode
+ *
+ * Hook for subclassers to post-process the node
+ * for the given object after encoding and return the
+ * post-processed node. This implementation returns
+ * the input node. The return value of this method
+ * is returned to the encoder from <encode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node that represents the default encoding.
+ */
+mxObjectCodec.prototype.afterEncode = function(enc, obj, node)
+{
+ return node;
+};
+
+/**
+ * Function: decode
+ *
+ * Parses the given node into the object or returns a new object
+ * representing the given node.
+ *
+ * Dec is a reference to the calling decoder. It is used to decode
+ * complex objects and resolve references.
+ *
+ * If a node has an id attribute then the object cache is checked for the
+ * object. If the object is not yet in the cache then it is constructed
+ * using the constructor of <template> and cached in <mxCodec.objects>.
+ *
+ * This implementation decodes all attributes and childs of a node
+ * according to the following rules:
+ *
+ * - If the variable name is in <exclude> or if the attribute name is "id"
+ * or "as" then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getObject> is used
+ * to replace the reference with an object.
+ * - The variable name is mapped using a reverse <mapping>.
+ * - If the value has a child node, then the codec is used to create a
+ * child object with the variable name taken from the "as" attribute.
+ * - If the object is an array and the variable name is empty then the
+ * value or child object is appended to the array.
+ * - If an add child has no value or the object is not an array then
+ * the child text content is evaluated using <mxUtils.eval>.
+ *
+ * For add nodes where the object is not an array and the variable name
+ * is defined, the default mechanism is used, allowing to override/add
+ * methods as follows:
+ *
+ * (code)
+ * <Object>
+ * <add as="hello"><![CDATA[
+ * function(arg1) {
+ * mxUtils.alert('Hello '+arg1);
+ * }
+ * ]]></add>
+ * </Object>
+ * (end)
+ *
+ * If no object exists for an ID in <idrefs> a warning is issued
+ * using <mxLog.warn>.
+ *
+ * Returns the resulting object that represents the given XML node
+ * or the object given to the method as the into parameter.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * into - Optional objec to encode the node into.
+ */
+mxObjectCodec.prototype.decode = function(dec, node, into)
+{
+ var id = node.getAttribute('id');
+ var obj = dec.objects[id];
+
+ if (obj == null)
+ {
+ obj = into || this.cloneTemplate();
+
+ if (id != null)
+ {
+ dec.putObject(id, obj);
+ }
+ }
+
+ node = this.beforeDecode(dec, node, obj);
+ this.decodeNode(dec, node, obj);
+
+ return this.afterDecode(dec, node, obj);
+};
+
+/**
+ * Function: decodeNode
+ *
+ * Calls <decodeAttributes> and <decodeChildren> for the given node.
+ */
+mxObjectCodec.prototype.decodeNode = function(dec, node, obj)
+{
+ if (node != null)
+ {
+ this.decodeAttributes(dec, node, obj);
+ this.decodeChildren(dec, node, obj);
+ }
+};
+
+/**
+ * Function: decodeAttributes
+ *
+ * Decodes all attributes of the given node using <decodeAttribute>.
+ */
+mxObjectCodec.prototype.decodeAttributes = function(dec, node, obj)
+{
+ var attrs = node.attributes;
+
+ if (attrs != null)
+ {
+ for (var i = 0; i < attrs.length; i++)
+ {
+ this.decodeAttribute(dec, attrs[i], obj);
+ }
+ }
+};
+
+/**
+ * Function: decodeAttribute
+ *
+ * Reads the given attribute into the specified object.
+ */
+mxObjectCodec.prototype.decodeAttribute = function(dec, attr, obj)
+{
+ var name = attr.nodeName;
+
+ if (name != 'as' && name != 'id')
+ {
+ // Converts the string true and false to their boolean values.
+ // This may require an additional check on the obj to see if
+ // the existing field is a boolean value or uninitialized, in
+ // which case we may want to convert true and false to a string.
+ var value = this.convertValueFromXml(attr.nodeValue);
+ var fieldname = this.getFieldName(name);
+
+ if (this.isReference(obj, fieldname, value, false))
+ {
+ var tmp = dec.getObject(value);
+
+ if (tmp == null)
+ {
+ mxLog.warn('mxObjectCodec.decode: No object for ' +
+ this.getName() + '.' + name + '=' + value);
+ return; // exit
+ }
+
+ value = tmp;
+ }
+
+ if (!this.isExcluded(obj, name, value, false))
+ {
+ //mxLog.debug(mxUtils.getFunctionName(obj.constructor)+'.'+name+'='+value);
+ obj[name] = value;
+ }
+ }
+};
+
+/**
+ * Function: decodeChildren
+ *
+ * Decodec all children of the given node using <decodeChild>.
+ */
+mxObjectCodec.prototype.decodeChildren = function(dec, node, obj)
+{
+ var child = node.firstChild;
+
+ while (child != null)
+ {
+ var tmp = child.nextSibling;
+
+ if (child.nodeType == mxConstants.NODETYPE_ELEMENT &&
+ !this.processInclude(dec, child, obj))
+ {
+ this.decodeChild(dec, child, obj);
+ }
+
+ child = tmp;
+ }
+};
+
+/**
+ * Function: decodeChild
+ *
+ * Reads the specified child into the given object.
+ */
+mxObjectCodec.prototype.decodeChild = function(dec, child, obj)
+{
+ var fieldname = this.getFieldName(child.getAttribute('as'));
+
+ if (fieldname == null ||
+ !this.isExcluded(obj, fieldname, child, false))
+ {
+ var template = this.getFieldTemplate(obj, fieldname, child);
+ var value = null;
+
+ if (child.nodeName == 'add')
+ {
+ value = child.getAttribute('value');
+
+ if (value == null)
+ {
+ value = mxUtils.eval(mxUtils.getTextContent(child));
+ //mxLog.debug('Decoded '+fieldname+' '+mxUtils.getTextContent(child));
+ }
+ }
+ else
+ {
+ value = dec.decode(child, template);
+ // mxLog.debug('Decoded '+node.nodeName+'.'+fieldname+'='+
+ // ((tmp != null) ? tmp.constructor.name : 'null'));
+ }
+
+ this.addObjectValue(obj, fieldname, value, template);
+ }
+};
+
+/**
+ * Function: getFieldTemplate
+ *
+ * Returns the template instance for the given field. This returns the
+ * value of the field, null if the value is an array or an empty collection
+ * if the value is a collection. The value is then used to populate the
+ * field for a new instance. For strongly typed languages it may be
+ * required to override this to return the correct collection instance
+ * based on the encoded child.
+ */
+mxObjectCodec.prototype.getFieldTemplate = function(obj, fieldname, child)
+{
+ var template = obj[fieldname];
+
+ // Non-empty arrays are replaced completely
+ if (template instanceof Array && template.length > 0)
+ {
+ template = null;
+ }
+
+ return template;
+};
+
+/**
+ * Function: addObjectValue
+ *
+ * Sets the decoded child node as a value of the given object. If the
+ * object is a map, then the value is added with the given fieldname as a
+ * key. If the fieldname is not empty, then setFieldValue is called or
+ * else, if the object is a collection, the value is added to the
+ * collection. For strongly typed languages it may be required to
+ * override this with the correct code to add an entry to an object.
+ */
+mxObjectCodec.prototype.addObjectValue = function(obj, fieldname, value, template)
+{
+ if (value != null && value != template)
+ {
+ if (fieldname != null && fieldname.length > 0)
+ {
+ obj[fieldname] = value;
+ }
+ else
+ {
+ obj.push(value);
+ }
+ //mxLog.debug('Decoded '+mxUtils.getFunctionName(obj.constructor)+'.'+fieldname+': '+value);
+ }
+};
+
+/**
+ * Function: processInclude
+ *
+ * Returns true if the given node is an include directive and
+ * executes the include by decoding the XML document. Returns
+ * false if the given node is not an include directive.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the encoding/decoding process.
+ * node - XML node to be checked.
+ * into - Optional object to pass-thru to the codec.
+ */
+mxObjectCodec.prototype.processInclude = function(dec, node, into)
+{
+ if (node.nodeName == 'include')
+ {
+ var name = node.getAttribute('name');
+
+ if (name != null)
+ {
+ try
+ {
+ var xml = mxUtils.load(name).getDocumentElement();
+
+ if (xml != null)
+ {
+ dec.decode(xml, into);
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: beforeDecode
+ *
+ * Hook for subclassers to pre-process the node for
+ * the specified object and return the node to be
+ * used for further processing by <decode>.
+ * The object is created based on the template in the
+ * calling method and is never null. This implementation
+ * returns the input node. The return value of this
+ * function is used in <decode> to perform
+ * the default decoding into the given object.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Object to encode the node into.
+ */
+mxObjectCodec.prototype.beforeDecode = function(dec, node, obj)
+{
+ return node;
+};
+
+/**
+ * Function: afterDecode
+ *
+ * Hook for subclassers to post-process the object after
+ * decoding. This implementation returns the given object
+ * without any changes. The return value of this method
+ * is returned to the decoder from <decode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * node - XML node to be decoded.
+ * obj - Object that represents the default decoding.
+ */
+mxObjectCodec.prototype.afterDecode = function(dec, node, obj)
+{
+ return obj;
+};
diff --git a/src/js/io/mxRootChangeCodec.js b/src/js/io/mxRootChangeCodec.js
new file mode 100644
index 0000000..fda613a
--- /dev/null
+++ b/src/js/io/mxRootChangeCodec.js
@@ -0,0 +1,83 @@
+/**
+ * $Id: mxRootChangeCodec.js,v 1.6 2010-09-15 14:38:51 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxRootChangeCodec
+ *
+ * Codec for <mxRootChange>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec> and
+ * the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ * - root
+ */
+ var codec = new mxObjectCodec(new mxRootChange(),
+ ['model', 'previous', 'root']);
+
+ /**
+ * Function: onEncode
+ *
+ * Encodes the child recursively.
+ */
+ codec.afterEncode = function(enc, obj, node)
+ {
+ enc.encodeCell(obj.root, node);
+
+ return node;
+ };
+
+ /**
+ * Function: beforeDecode
+ *
+ * Decodes the optional children as cells
+ * using the respective decoder.
+ */
+ codec.beforeDecode = function(dec, node, obj)
+ {
+ if (node.firstChild != null &&
+ node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Makes sure the original node isn't modified
+ node = node.cloneNode(true);
+
+ var tmp = node.firstChild;
+ obj.root = dec.decodeCell(tmp, false);
+
+ var tmp2 = tmp.nextSibling;
+ tmp.parentNode.removeChild(tmp);
+ tmp = tmp2;
+
+ while (tmp != null)
+ {
+ tmp2 = tmp.nextSibling;
+ dec.decodeCell(tmp);
+ tmp.parentNode.removeChild(tmp);
+ tmp = tmp2;
+ }
+ }
+
+ return node;
+ };
+
+ /**
+ * Function: afterDecode
+ *
+ * Restores the state by assigning the previous value.
+ */
+ codec.afterDecode = function(dec, node, obj)
+ {
+ obj.previous = obj.root;
+
+ return obj;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxStylesheetCodec.js b/src/js/io/mxStylesheetCodec.js
new file mode 100644
index 0000000..7636eb1
--- /dev/null
+++ b/src/js/io/mxStylesheetCodec.js
@@ -0,0 +1,210 @@
+/**
+ * $Id: mxStylesheetCodec.js,v 1.19 2011-06-13 08:18:42 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxStylesheetCodec
+ *
+ * Codec for <mxStylesheet>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ */
+ var codec = new mxObjectCodec(new mxStylesheet());
+
+ /**
+ * Function: encode
+ *
+ * Encodes a stylesheet. See <decode> for a description of the
+ * format.
+ */
+ codec.encode = function(enc, obj)
+ {
+ var node = enc.document.createElement(this.getName());
+
+ for (var i in obj.styles)
+ {
+ var style = obj.styles[i];
+ var styleNode = enc.document.createElement('add');
+
+ if (i != null)
+ {
+ styleNode.setAttribute('as', i);
+
+ for (var j in style)
+ {
+ var value = this.getStringValue(j, style[j]);
+
+ if (value != null)
+ {
+ var entry = enc.document.createElement('add');
+ entry.setAttribute('value', value);
+ entry.setAttribute('as', j);
+ styleNode.appendChild(entry);
+ }
+ }
+
+ if (styleNode.childNodes.length > 0)
+ {
+ node.appendChild(styleNode);
+ }
+ }
+ }
+
+ return node;
+ };
+
+ /**
+ * Function: getStringValue
+ *
+ * Returns the string for encoding the given value.
+ */
+ codec.getStringValue = function(key, value)
+ {
+ var type = typeof(value);
+
+ if (type == 'function')
+ {
+ value = mxStyleRegistry.getName(style[j]);
+ }
+ else if (type == 'object')
+ {
+ value = null;
+ }
+
+ return value;
+ };
+
+ /**
+ * Function: decode
+ *
+ * Reads a sequence of the following child nodes
+ * and attributes:
+ *
+ * Child Nodes:
+ *
+ * add - Adds a new style.
+ *
+ * Attributes:
+ *
+ * as - Name of the style.
+ * extend - Name of the style to inherit from.
+ *
+ * Each node contains another sequence of add and remove nodes with the following
+ * attributes:
+ *
+ * as - Name of the style (see <mxConstants>).
+ * value - Value for the style.
+ *
+ * Instead of the value-attribute, one can put Javascript expressions into
+ * the node as follows:
+ * <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
+ *
+ * A remove node will remove the entry with the name given in the as-attribute
+ * from the style.
+ *
+ * Example:
+ *
+ * (code)
+ * <mxStylesheet as="stylesheet">
+ * <add as="text">
+ * <add as="fontSize" value="12"/>
+ * </add>
+ * <add as="defaultVertex" extend="text">
+ * <add as="shape" value="rectangle"/>
+ * </add>
+ * </mxStylesheet>
+ * (end)
+ */
+ codec.decode = function(dec, node, into)
+ {
+ var obj = into || new this.template.constructor();
+ var id = node.getAttribute('id');
+
+ if (id != null)
+ {
+ dec.objects[id] = obj;
+ }
+
+ node = node.firstChild;
+
+ while (node != null)
+ {
+ if (!this.processInclude(dec, node, obj) &&
+ node.nodeName == 'add')
+ {
+ var as = node.getAttribute('as');
+
+ if (as != null)
+ {
+ var extend = node.getAttribute('extend');
+ var style = (extend != null) ? mxUtils.clone(obj.styles[extend]) : null;
+
+ if (style == null)
+ {
+ if (extend != null)
+ {
+ mxLog.warn('mxStylesheetCodec.decode: stylesheet ' +
+ extend + ' not found to extend');
+ }
+
+ style = new Object();
+ }
+
+ var entry = node.firstChild;
+
+ while (entry != null)
+ {
+ if (entry.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ var key = entry.getAttribute('as');
+
+ if (entry.nodeName == 'add')
+ {
+ var text = mxUtils.getTextContent(entry);
+ var value = null;
+
+ if (text != null &&
+ text.length > 0)
+ {
+ value = mxUtils.eval(text);
+ }
+ else
+ {
+ value = entry.getAttribute('value');
+
+ if (mxUtils.isNumeric(value))
+ {
+ value = parseFloat(value);
+ }
+ }
+
+ if (value != null)
+ {
+ style[key] = value;
+ }
+ }
+ else if (entry.nodeName == 'remove')
+ {
+ delete style[key];
+ }
+ }
+
+ entry = entry.nextSibling;
+ }
+
+ obj.putCellStyle(as, style);
+ }
+ }
+
+ node = node.nextSibling;
+ }
+
+ return obj;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxTerminalChangeCodec.js b/src/js/io/mxTerminalChangeCodec.js
new file mode 100644
index 0000000..a51d871
--- /dev/null
+++ b/src/js/io/mxTerminalChangeCodec.js
@@ -0,0 +1,42 @@
+/**
+ * $Id: mxTerminalChangeCodec.js,v 1.7 2010-09-13 15:58:36 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxTerminalChangeCodec
+ *
+ * Codec for <mxTerminalChange>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec> and
+ * the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ *
+ * Reference Fields:
+ *
+ * - cell
+ * - terminal
+ */
+ var codec = new mxObjectCodec(new mxTerminalChange(),
+ ['model', 'previous'], ['cell', 'terminal']);
+
+ /**
+ * Function: afterDecode
+ *
+ * Restores the state by assigning the previous value.
+ */
+ codec.afterDecode = function(dec, node, obj)
+ {
+ obj.previous = obj.terminal;
+
+ return obj;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js b/src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
new file mode 100644
index 0000000..e2fe6a6
--- /dev/null
+++ b/src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
@@ -0,0 +1,206 @@
+/**
+ * $Id: mxGraphAbstractHierarchyCell.js,v 1.12 2010-01-04 11:18:26 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphAbstractHierarchyCell
+ *
+ * An abstraction of an internal hierarchy node or edge
+ *
+ * Constructor: mxGraphAbstractHierarchyCell
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxGraphAbstractHierarchyCell()
+{
+ this.x = [];
+ this.y = [];
+ this.temp = [];
+};
+
+/**
+ * Variable: maxRank
+ *
+ * The maximum rank this cell occupies. Default is -1.
+ */
+mxGraphAbstractHierarchyCell.prototype.maxRank = -1;
+
+/**
+ * Variable: minRank
+ *
+ * The minimum rank this cell occupies. Default is -1.
+ */
+mxGraphAbstractHierarchyCell.prototype.minRank = -1;
+
+/**
+ * Variable: x
+ *
+ * The x position of this cell for each layer it occupies
+ */
+mxGraphAbstractHierarchyCell.prototype.x = null;
+
+/**
+ * Variable: y
+ *
+ * The y position of this cell for each layer it occupies
+ */
+mxGraphAbstractHierarchyCell.prototype.y = null;
+
+/**
+ * Variable: width
+ *
+ * The width of this cell
+ */
+mxGraphAbstractHierarchyCell.prototype.width = 0;
+
+/**
+ * Variable: height
+ *
+ * The height of this cell
+ */
+mxGraphAbstractHierarchyCell.prototype.height = 0;
+
+/**
+ * Variable: nextLayerConnectedCells
+ *
+ * A cached version of the cells this cell connects to on the next layer up
+ */
+mxGraphAbstractHierarchyCell.prototype.nextLayerConnectedCells = null;
+
+/**
+ * Variable: previousLayerConnectedCells
+ *
+ * A cached version of the cells this cell connects to on the next layer down
+ */
+mxGraphAbstractHierarchyCell.prototype.previousLayerConnectedCells = null;
+
+/**
+ * Variable: temp
+ *
+ * Temporary variable for general use. Generally, try to avoid
+ * carrying information between stages. Currently, the longest
+ * path layering sets temp to the rank position in fixRanks()
+ * and the crossing reduction uses this. This meant temp couldn't
+ * be used for hashing the nodes in the model dfs and so hashCode
+ * was created
+ */
+mxGraphAbstractHierarchyCell.prototype.temp = null;
+
+/**
+ * Function: getNextLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphAbstractHierarchyCell.prototype.getNextLayerConnectedCells = function(layer)
+{
+ return null;
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphAbstractHierarchyCell.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+ return null;
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns whether or not this cell is an edge
+ */
+mxGraphAbstractHierarchyCell.prototype.isEdge = function()
+{
+ return false;
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns whether or not this cell is a node
+ */
+mxGraphAbstractHierarchyCell.prototype.isVertex = function()
+{
+ return false;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ *
+ * Gets the value of temp for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.getGeneralPurposeVariable = function(layer)
+{
+ return null;
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ *
+ * Set the value of temp for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+ return null;
+};
+
+/**
+ * Function: setX
+ *
+ * Set the value of x for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setX = function(layer, value)
+{
+ if (this.isVertex())
+ {
+ this.x[0] = value;
+ }
+ else if (this.isEdge())
+ {
+ this.x[layer - this.minRank - 1] = value;
+ }
+};
+
+/**
+ * Function: getX
+ *
+ * Gets the value of x on the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.getX = function(layer)
+{
+ if (this.isVertex())
+ {
+ return this.x[0];
+ }
+ else if (this.isEdge())
+ {
+ return this.x[layer - this.minRank - 1];
+ }
+
+ return 0.0;
+};
+
+/**
+ * Function: setY
+ *
+ * Set the value of y for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setY = function(layer, value)
+{
+ if (this.isVertex())
+ {
+ this.y[0] = value;
+ }
+ else if (this.isEdge())
+ {
+ this.y[layer -this. minRank - 1] = value;
+ }
+};
diff --git a/src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js b/src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
new file mode 100644
index 0000000..8ba16dd
--- /dev/null
+++ b/src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
@@ -0,0 +1,174 @@
+/**
+ * $Id: mxGraphHierarchyEdge.js,v 1.15 2012-06-12 20:23:14 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphHierarchyEdge
+ *
+ * An abstraction of a hierarchical edge for the hierarchy layout
+ *
+ * Constructor: mxGraphHierarchyEdge
+ *
+ * Constructs a hierarchy edge
+ *
+ * Arguments:
+ *
+ * edges - a list of real graph edges this abstraction represents
+ */
+function mxGraphHierarchyEdge(edges)
+{
+ mxGraphAbstractHierarchyCell.apply(this, arguments);
+ this.edges = edges;
+};
+
+/**
+ * Extends mxGraphAbstractHierarchyCell.
+ */
+mxGraphHierarchyEdge.prototype = new mxGraphAbstractHierarchyCell();
+mxGraphHierarchyEdge.prototype.constructor = mxGraphHierarchyEdge;
+
+/**
+ * Variable: edges
+ *
+ * The graph edge(s) this object represents. Parallel edges are all grouped
+ * together within one hierarchy edge.
+ */
+mxGraphHierarchyEdge.prototype.edges = null;
+
+/**
+ * Variable: source
+ *
+ * The node this edge is sourced at
+ */
+mxGraphHierarchyEdge.prototype.source = null;
+
+/**
+ * Variable: target
+ *
+ * The node this edge targets
+ */
+mxGraphHierarchyEdge.prototype.target = null;
+
+/**
+ * Variable: isReversed
+ *
+ * Whether or not the direction of this edge has been reversed
+ * internally to create a DAG for the hierarchical layout
+ */
+mxGraphHierarchyEdge.prototype.isReversed = false;
+
+/**
+ * Function: invert
+ *
+ * Inverts the direction of this internal edge(s)
+ */
+mxGraphHierarchyEdge.prototype.invert = function(layer)
+{
+ var temp = this.source;
+ this.source = this.target;
+ this.target = temp;
+ this.isReversed = !this.isReversed;
+};
+
+/**
+ * Function: getNextLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphHierarchyEdge.prototype.getNextLayerConnectedCells = function(layer)
+{
+ if (this.nextLayerConnectedCells == null)
+ {
+ this.nextLayerConnectedCells = [];
+
+ for (var i = 0; i < this.temp.length; i++)
+ {
+ this.nextLayerConnectedCells[i] = [];
+
+ if (i == this.temp.length - 1)
+ {
+ this.nextLayerConnectedCells[i].push(this.source);
+ }
+ else
+ {
+ this.nextLayerConnectedCells[i].push(this);
+ }
+ }
+ }
+
+ return this.nextLayerConnectedCells[layer - this.minRank - 1];
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphHierarchyEdge.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+ if (this.previousLayerConnectedCells == null)
+ {
+ this.previousLayerConnectedCells = [];
+
+ for (var i = 0; i < this.temp.length; i++)
+ {
+ this.previousLayerConnectedCells[i] = [];
+
+ if (i == 0)
+ {
+ this.previousLayerConnectedCells[i].push(this.target);
+ }
+ else
+ {
+ this.previousLayerConnectedCells[i].push(this);
+ }
+ }
+ }
+
+ return this.previousLayerConnectedCells[layer - this.minRank - 1];
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns true.
+ */
+mxGraphHierarchyEdge.prototype.isEdge = function()
+{
+ return true;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ *
+ * Gets the value of temp for the specified layer
+ */
+mxGraphHierarchyEdge.prototype.getGeneralPurposeVariable = function(layer)
+{
+ return this.temp[layer - this.minRank - 1];
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ *
+ * Set the value of temp for the specified layer
+ */
+mxGraphHierarchyEdge.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+ this.temp[layer - this.minRank - 1] = value;
+};
+
+/**
+ * Function: getCoreCell
+ *
+ * Gets the first core edge associated with this wrapper
+ */
+mxGraphHierarchyEdge.prototype.getCoreCell = function()
+{
+ if (this.edges != null && this.edges.length > 0)
+ {
+ return this.edges[0];
+ }
+
+ return null;
+}; \ No newline at end of file
diff --git a/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js b/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js
new file mode 100644
index 0000000..ca2ba30
--- /dev/null
+++ b/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js
@@ -0,0 +1,685 @@
+/**
+ * $Id: mxGraphHierarchyModel.js,v 1.33 2012-12-18 13:16:43 david Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Class: mxGraphHierarchyModel
+ *
+ * Internal model of a hierarchical graph. This model stores nodes and edges
+ * equivalent to the real graph nodes and edges, but also stores the rank of the
+ * cells, the order within the ranks and the new candidate locations of cells.
+ * The internal model also reverses edge direction were appropriate , ignores
+ * self-loop and groups parallels together under one edge object.
+ *
+ * Constructor: mxGraphHierarchyModel
+ *
+ * Creates an internal ordered graph model using the vertices passed in. If
+ * there are any, leftward edge need to be inverted in the internal model
+ *
+ * Arguments:
+ *
+ * graph - the facade describing the graph to be operated on
+ * vertices - the vertices for this hierarchy
+ * ordered - whether or not the vertices are already ordered
+ * deterministic - whether or not this layout should be deterministic on each
+ * tightenToSource - whether or not to tighten vertices towards the sources
+ * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
+ * usage
+ */
+function mxGraphHierarchyModel(layout, vertices, roots, parent, tightenToSource)
+{
+ var graph = layout.getGraph();
+ this.tightenToSource = tightenToSource;
+ this.roots = roots;
+ this.parent = parent;
+
+ // map of cells to internal cell needed for second run through
+ // to setup the sink of edges correctly
+ this.vertexMapper = new Object();
+ this.edgeMapper = new Object();
+ this.maxRank = 0;
+ var internalVertices = [];
+
+ if (vertices == null)
+ {
+ vertices = this.graph.getChildVertices(parent);
+ }
+
+ this.maxRank = this.SOURCESCANSTARTRANK;
+ // map of cells to internal cell needed for second run through
+ // to setup the sink of edges correctly. Guess size by number
+ // of edges is roughly same as number of vertices.
+ this.createInternalCells(layout, vertices, internalVertices);
+
+ // Go through edges set their sink values. Also check the
+ // ordering if and invert edges if necessary
+ for (var i = 0; i < vertices.length; i++)
+ {
+ var edges = internalVertices[i].connectsAsSource;
+
+ for (var j = 0; j < edges.length; j++)
+ {
+ var internalEdge = edges[j];
+ var realEdges = internalEdge.edges;
+
+ // Only need to process the first real edge, since
+ // all the edges connect to the same other vertex
+ if (realEdges != null && realEdges.length > 0)
+ {
+ var realEdge = realEdges[0];
+ var targetCell = graph.getView().getVisibleTerminal(
+ realEdge, false);
+ var targetCellId = mxCellPath.create(targetCell);
+ var internalTargetCell = this.vertexMapper[targetCellId];
+
+ if (internalVertices[i] == internalTargetCell)
+ {
+ // The real edge is reversed relative to the internal edge
+ targetCell = graph.getView().getVisibleTerminal(
+ realEdge, true);
+ targetCellId = mxCellPath.create(targetCell);
+ internalTargetCell = this.vertexMapper[targetCellId];
+ }
+
+ if (internalTargetCell != null
+ && internalVertices[i] != internalTargetCell)
+ {
+ internalEdge.target = internalTargetCell;
+
+ if (internalTargetCell.connectsAsTarget.length == 0)
+ {
+ internalTargetCell.connectsAsTarget = [];
+ }
+
+ if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)
+ {
+ internalTargetCell.connectsAsTarget.push(internalEdge);
+ }
+ }
+ }
+ }
+
+ // Use the temp variable in the internal nodes to mark this
+ // internal vertex as having been visited.
+ internalVertices[i].temp[0] = 1;
+ }
+};
+
+/**
+ * Variable: maxRank
+ *
+ * Stores the largest rank number allocated
+ */
+mxGraphHierarchyModel.prototype.maxRank = null;
+
+/**
+ * Variable: vertexMapper
+ *
+ * Map from graph vertices to internal model nodes.
+ */
+mxGraphHierarchyModel.prototype.vertexMapper = null;
+
+/**
+ * Variable: edgeMapper
+ *
+ * Map from graph edges to internal model edges
+ */
+mxGraphHierarchyModel.prototype.edgeMapper = null;
+
+/**
+ * Variable: ranks
+ *
+ * Mapping from rank number to actual rank
+ */
+mxGraphHierarchyModel.prototype.ranks = null;
+
+/**
+ * Variable: roots
+ *
+ * Store of roots of this hierarchy model, these are real graph cells, not
+ * internal cells
+ */
+mxGraphHierarchyModel.prototype.roots = null;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell whose children are being laid out
+ */
+mxGraphHierarchyModel.prototype.parent = null;
+
+/**
+ * Variable: dfsCount
+ *
+ * Count of the number of times the ancestor dfs has been used.
+ */
+mxGraphHierarchyModel.prototype.dfsCount = 0;
+
+/**
+ * Variable: SOURCESCANSTARTRANK
+ *
+ * High value to start source layering scan rank value from.
+ */
+mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK = 100000000;
+
+/**
+ * Variable: tightenToSource
+ *
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxGraphHierarchyModel.prototype.tightenToSource = false;
+
+/**
+ * Function: createInternalCells
+ *
+ * Creates all edges in the internal model
+ *
+ * Parameters:
+ *
+ * layout - Reference to the <mxHierarchicalLayout> algorithm.
+ * vertices - Array of <mxCells> that represent the vertices whom are to
+ * have an internal representation created.
+ * internalVertices - The array of <mxGraphHierarchyNodes> to have their
+ * information filled in using the real vertices.
+ */
+mxGraphHierarchyModel.prototype.createInternalCells = function(layout, vertices, internalVertices)
+{
+ var graph = layout.getGraph();
+
+ // Create internal edges
+ for (var i = 0; i < vertices.length; i++)
+ {
+ internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
+ var vertexId = mxCellPath.create(vertices[i]);
+ this.vertexMapper[vertexId] = internalVertices[i];
+
+ // If the layout is deterministic, order the cells
+ //List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
+ var conns = layout.getEdges(vertices[i]);
+ var outgoingCells = graph.getOpposites(conns, vertices[i]);
+ internalVertices[i].connectsAsSource = [];
+
+ // Create internal edges, but don't do any rank assignment yet
+ // First use the information from the greedy cycle remover to
+ // invert the leftward edges internally
+ for (var j = 0; j < outgoingCells.length; j++)
+ {
+ var cell = outgoingCells[j];
+
+ if (cell != vertices[i] && layout.graph.model.isVertex(cell) &&
+ !layout.isVertexIgnored(cell))
+ {
+ // We process all edge between this source and its targets
+ // If there are edges going both ways, we need to collect
+ // them all into one internal edges to avoid looping problems
+ // later. We assume this direction (source -> target) is the
+ // natural direction if at least half the edges are going in
+ // that direction.
+
+ // The check below for edges[0] being in the vertex mapper is
+ // in case we've processed this the other way around
+ // (target -> source) and the number of edges in each direction
+ // are the same. All the graph edges will have been assigned to
+ // an internal edge going the other way, so we don't want to
+ // process them again
+ var undirectedEdges = graph.getEdgesBetween(vertices[i],
+ cell, false);
+ var directedEdges = graph.getEdgesBetween(vertices[i],
+ cell, true);
+ var edgeId = mxCellPath.create(undirectedEdges[0]);
+
+ if (undirectedEdges != null &&
+ undirectedEdges.length > 0 &&
+ this.edgeMapper[edgeId] == null &&
+ directedEdges.length * 2 >= undirectedEdges.length)
+ {
+ var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);
+
+ for (var k = 0; k < undirectedEdges.length; k++)
+ {
+ var edge = undirectedEdges[k];
+ edgeId = mxCellPath.create(edge);
+ this.edgeMapper[edgeId] = internalEdge;
+
+ // Resets all point on the edge and disables the edge style
+ // without deleting it from the cell style
+ graph.resetEdge(edge);
+
+ if (layout.disableEdgeStyle)
+ {
+ layout.setEdgeStyleEnabled(edge, false);
+ layout.setOrthogonalEdge(edge,true);
+ }
+ }
+
+ internalEdge.source = internalVertices[i];
+
+ if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)
+ {
+ internalVertices[i].connectsAsSource.push(internalEdge);
+ }
+ }
+ }
+ }
+
+ // Ensure temp variable is cleared from any previous use
+ internalVertices[i].temp[0] = 0;
+ }
+};
+
+/**
+ * Function: initialRank
+ *
+ * Basic determination of minimum layer ranking by working from from sources
+ * or sinks and working through each node in the relevant edge direction.
+ * Starting at the sinks is basically a longest path layering algorithm.
+*/
+mxGraphHierarchyModel.prototype.initialRank = function()
+{
+ var startNodes = [];
+
+ if (this.roots != null)
+ {
+ for (var i = 0; i < this.roots.length; i++)
+ {
+ var vertexId = mxCellPath.create(this.roots[i]);
+ var internalNode = this.vertexMapper[vertexId];
+
+ if (internalNode != null)
+ {
+ startNodes.push(internalNode);
+ }
+ }
+ }
+
+ for (var key in this.vertexMapper)
+ {
+ var internalNode = this.vertexMapper[key];
+
+ // Mark the node as not having had a layer assigned
+ internalNode.temp[0] = -1;
+ }
+
+ var startNodesCopy = startNodes.slice();
+
+ while (startNodes.length > 0)
+ {
+ var internalNode = startNodes[0];
+ var layerDeterminingEdges;
+ var edgesToBeMarked;
+
+ layerDeterminingEdges = internalNode.connectsAsTarget;
+ edgesToBeMarked = internalNode.connectsAsSource;
+
+ // flag to keep track of whether or not all layer determining
+ // edges have been scanned
+ var allEdgesScanned = true;
+
+ // Work out the layer of this node from the layer determining
+ // edges. The minimum layer number of any node connected by one of
+ // the layer determining edges variable
+ var minimumLayer = this.SOURCESCANSTARTRANK;
+
+ for (var i = 0; i < layerDeterminingEdges.length; i++)
+ {
+ var internalEdge = layerDeterminingEdges[i];
+
+ if (internalEdge.temp[0] == 5270620)
+ {
+ // This edge has been scanned, get the layer of the
+ // node on the other end
+ var otherNode = internalEdge.source;
+ minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
+ }
+ else
+ {
+ allEdgesScanned = false;
+
+ break;
+ }
+ }
+
+ // If all edge have been scanned, assign the layer, mark all
+ // edges in the other direction and remove from the nodes list
+ if (allEdgesScanned)
+ {
+ internalNode.temp[0] = minimumLayer;
+ this.maxRank = Math.min(this.maxRank, minimumLayer);
+
+ if (edgesToBeMarked != null)
+ {
+ for (var i = 0; i < edgesToBeMarked.length; i++)
+ {
+ var internalEdge = edgesToBeMarked[i];
+
+ // Assign unique stamp ( y/m/d/h )
+ internalEdge.temp[0] = 5270620;
+
+ // Add node on other end of edge to LinkedList of
+ // nodes to be analysed
+ var otherNode = internalEdge.target;
+
+ // Only add node if it hasn't been assigned a layer
+ if (otherNode.temp[0] == -1)
+ {
+ startNodes.push(otherNode);
+
+ // Mark this other node as neither being
+ // unassigned nor assigned so it isn't
+ // added to this list again, but it's
+ // layer isn't used in any calculation.
+ otherNode.temp[0] = -2;
+ }
+ }
+ }
+
+ startNodes.shift();
+ }
+ else
+ {
+ // Not all the edges have been scanned, get to the back of
+ // the class and put the dunces cap on
+ var removedCell = startNodes.shift();
+ startNodes.push(internalNode);
+
+ if (removedCell == internalNode && startNodes.length == 1)
+ {
+ // This is an error condition, we can't get out of
+ // this loop. It could happen for more than one node
+ // but that's a lot harder to detect. Log the error
+ // TODO make log comment
+ break;
+ }
+ }
+ }
+
+ // Normalize the ranks down from their large starting value to place
+ // at least 1 sink on layer 0
+ for (var key in this.vertexMapper)
+ {
+ var internalNode = this.vertexMapper[key];
+ // Mark the node as not having had a layer assigned
+ internalNode.temp[0] -= this.maxRank;
+ }
+
+ // Tighten the rank 0 nodes as far as possible
+ for ( var i = 0; i < startNodesCopy.length; i++)
+ {
+ var internalNode = startNodesCopy[i];
+ var currentMaxLayer = 0;
+ var layerDeterminingEdges = internalNode.connectsAsSource;
+
+ for ( var j = 0; j < layerDeterminingEdges.length; j++)
+ {
+ var internalEdge = layerDeterminingEdges[j];
+ var otherNode = internalEdge.target;
+ internalNode.temp[0] = Math.max(currentMaxLayer,
+ otherNode.temp[0] + 1);
+ currentMaxLayer = internalNode.temp[0];
+ }
+ }
+
+ // Reset the maxRank to that which would be expected for a from-sink
+ // scan
+ this.maxRank = this.SOURCESCANSTARTRANK - this.maxRank;
+};
+
+/**
+ * Function: fixRanks
+ *
+ * Fixes the layer assignments to the values stored in the nodes. Also needs
+ * to create dummy nodes for edges that cross layers.
+ */
+mxGraphHierarchyModel.prototype.fixRanks = function()
+{
+ var rankList = [];
+ this.ranks = [];
+
+ for (var i = 0; i < this.maxRank + 1; i++)
+ {
+ rankList[i] = [];
+ this.ranks[i] = rankList[i];
+ }
+
+ // Perform a DFS to obtain an initial ordering for each rank.
+ // Without doing this you would end up having to process
+ // crossings for a standard tree.
+ var rootsArray = null;
+
+ if (this.roots != null)
+ {
+ var oldRootsArray = this.roots;
+ rootsArray = [];
+
+ for (var i = 0; i < oldRootsArray.length; i++)
+ {
+ var cell = oldRootsArray[i];
+ var cellId = mxCellPath.create(cell);
+ var internalNode = this.vertexMapper[cellId];
+ rootsArray[i] = internalNode;
+ }
+ }
+
+ this.visit(function(parent, node, edge, layer, seen)
+ {
+ if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
+ {
+ rankList[node.temp[0]].push(node);
+ node.maxRank = node.temp[0];
+ node.minRank = node.temp[0];
+
+ // Set temp[0] to the nodes position in the rank
+ node.temp[0] = rankList[node.maxRank].length - 1;
+ }
+
+ if (parent != null && edge != null)
+ {
+ var parentToCellRankDifference = parent.maxRank - node.maxRank;
+
+ if (parentToCellRankDifference > 1)
+ {
+ // There are ranks in between the parent and current cell
+ edge.maxRank = parent.maxRank;
+ edge.minRank = node.maxRank;
+ edge.temp = [];
+ edge.x = [];
+ edge.y = [];
+
+ for (var i = edge.minRank + 1; i < edge.maxRank; i++)
+ {
+ // The connecting edge must be added to the
+ // appropriate ranks
+ rankList[i].push(edge);
+ edge.setGeneralPurposeVariable(i, rankList[i]
+ .length - 1);
+ }
+ }
+ }
+ }, rootsArray, false, null);
+};
+
+/**
+ * Function: visit
+ *
+ * A depth first search through the internal heirarchy model.
+ *
+ * Parameters:
+ *
+ * visitor - The visitor function pattern to be called for each node.
+ * trackAncestors - Whether or not the search is to keep track all nodes
+ * directly above this one in the search path.
+ */
+mxGraphHierarchyModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)
+{
+ // Run dfs through on all roots
+ if (dfsRoots != null)
+ {
+ for (var i = 0; i < dfsRoots.length; i++)
+ {
+ var internalNode = dfsRoots[i];
+
+ if (internalNode != null)
+ {
+ if (seenNodes == null)
+ {
+ seenNodes = new Object();
+ }
+
+ if (trackAncestors)
+ {
+ // Set up hash code for root
+ internalNode.hashCode = [];
+ internalNode.hashCode[0] = this.dfsCount;
+ internalNode.hashCode[1] = i;
+ this.extendedDfs(null, internalNode, null, visitor, seenNodes,
+ internalNode.hashCode, i, 0);
+ }
+ else
+ {
+ this.dfs(null, internalNode, null, visitor, seenNodes, 0);
+ }
+ }
+ }
+
+ this.dfsCount++;
+ }
+};
+
+/**
+ * Function: dfs
+ *
+ * Performs a depth first search on the internal hierarchy model
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs a set of all of the
+ * ancestor node of the current node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxGraphHierarchyModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)
+{
+ if (root != null)
+ {
+ var rootId = mxCellPath.create(root.cell);
+
+ if (seen[rootId] == null)
+ {
+ seen[rootId] = root;
+ visitor(parent, root, connectingEdge, layer, 0);
+
+ // Copy the connects as source list so that visitors
+ // can change the original for edge direction inversions
+ var outgoingEdges = root.connectsAsSource.slice();
+
+ for (var i = 0; i< outgoingEdges.length; i++)
+ {
+ var internalEdge = outgoingEdges[i];
+ var targetNode = internalEdge.target;
+
+ // Root check is O(|roots|)
+ this.dfs(root, targetNode, internalEdge, visitor, seen,
+ layer + 1);
+ }
+ }
+ else
+ {
+ // Use the int field to indicate this node has been seen
+ visitor(parent, root, connectingEdge, layer, 1);
+ }
+ }
+};
+
+/**
+ * Function: extendedDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of cells ancestors, but it
+ * should be only used when necessary because of it can be computationally
+ * intensive for deep searches.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs
+ * ancestors - the parent hash code
+ * childHash - the new hash code for this node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxGraphHierarchyModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)
+{
+ // Explanation of custom hash set. Previously, the ancestors variable
+ // was passed through the dfs as a HashSet. The ancestors were copied
+ // into a new HashSet and when the new child was processed it was also
+ // added to the set. If the current node was in its ancestor list it
+ // meant there is a cycle in the graph and this information is passed
+ // to the visitor.visit() in the seen parameter. The HashSet clone was
+ // very expensive on CPU so a custom hash was developed using primitive
+ // types. temp[] couldn't be used so hashCode[] was added to each node.
+ // Each new child adds another int to the array, copying the prefix
+ // from its parent. Child of the same parent add different ints (the
+ // limit is therefore 2^32 children per parent...). If a node has a
+ // child with the hashCode already set then the child code is compared
+ // to the same portion of the current nodes array. If they match there
+ // is a loop.
+ // Note that the basic mechanism would only allow for 1 use of this
+ // functionality, so the root nodes have two ints. The second int is
+ // incremented through each node root and the first is incremented
+ // through each run of the dfs algorithm (therefore the dfs is not
+ // thread safe). The hash code of each node is set if not already set,
+ // or if the first int does not match that of the current run.
+ if (root != null)
+ {
+ if (parent != null)
+ {
+ // Form this nodes hash code if necessary, that is, if the
+ // hashCode variable has not been initialized or if the
+ // start of the parent hash code does not equal the start of
+ // this nodes hash code, indicating the code was set on a
+ // previous run of this dfs.
+ if (root.hashCode == null ||
+ root.hashCode[0] != parent.hashCode[0])
+ {
+ var hashCodeLength = parent.hashCode.length + 1;
+ root.hashCode = parent.hashCode.slice();
+ root.hashCode[hashCodeLength - 1] = childHash;
+ }
+ }
+
+ var rootId = mxCellPath.create(root.cell);
+
+ if (seen[rootId] == null)
+ {
+ seen[rootId] = root;
+ visitor(parent, root, connectingEdge, layer, 0);
+
+ // Copy the connects as source list so that visitors
+ // can change the original for edge direction inversions
+ var outgoingEdges = root.connectsAsSource.slice();
+
+ for (var i = 0; i < outgoingEdges.length; i++)
+ {
+ var internalEdge = outgoingEdges[i];
+ var targetNode = internalEdge.target;
+
+ // Root check is O(|roots|)
+ this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+ root.hashCode, i, layer + 1);
+ }
+ }
+ else
+ {
+ // Use the int field to indicate this node has been seen
+ visitor(parent, root, connectingEdge, layer, 1);
+ }
+ }
+};
diff --git a/src/js/layout/hierarchical/model/mxGraphHierarchyNode.js b/src/js/layout/hierarchical/model/mxGraphHierarchyNode.js
new file mode 100644
index 0000000..d901d57
--- /dev/null
+++ b/src/js/layout/hierarchical/model/mxGraphHierarchyNode.js
@@ -0,0 +1,210 @@
+/**
+ * $Id: mxGraphHierarchyNode.js,v 1.13 2012-06-12 20:24:58 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphHierarchyNode
+ *
+ * An abstraction of a hierarchical edge for the hierarchy layout
+ *
+ * Constructor: mxGraphHierarchyNode
+ *
+ * Constructs an internal node to represent the specified real graph cell
+ *
+ * Arguments:
+ *
+ * cell - the real graph cell this node represents
+ */
+function mxGraphHierarchyNode(cell)
+{
+ mxGraphAbstractHierarchyCell.apply(this, arguments);
+ this.cell = cell;
+};
+
+/**
+ * Extends mxGraphAbstractHierarchyCell.
+ */
+mxGraphHierarchyNode.prototype = new mxGraphAbstractHierarchyCell();
+mxGraphHierarchyNode.prototype.constructor = mxGraphHierarchyNode;
+
+/**
+ * Variable: cell
+ *
+ * The graph cell this object represents.
+ */
+mxGraphHierarchyNode.prototype.cell = null;
+
+/**
+ * Variable: connectsAsTarget
+ *
+ * Collection of hierarchy edges that have this node as a target
+ */
+mxGraphHierarchyNode.prototype.connectsAsTarget = [];
+
+/**
+ * Variable: connectsAsSource
+ *
+ * Collection of hierarchy edges that have this node as a source
+ */
+mxGraphHierarchyNode.prototype.connectsAsSource = [];
+
+/**
+ * Variable: hashCode
+ *
+ * Assigns a unique hashcode for each node. Used by the model dfs instead
+ * of copying HashSets
+ */
+mxGraphHierarchyNode.prototype.hashCode = false;
+
+/**
+ * Function: getRankValue
+ *
+ * Returns the integer value of the layer that this node resides in
+ */
+mxGraphHierarchyNode.prototype.getRankValue = function(layer)
+{
+ return this.maxRank;
+};
+
+/**
+ * Function: getNextLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphHierarchyNode.prototype.getNextLayerConnectedCells = function(layer)
+{
+ if (this.nextLayerConnectedCells == null)
+ {
+ this.nextLayerConnectedCells = [];
+ this.nextLayerConnectedCells[0] = [];
+
+ for (var i = 0; i < this.connectsAsTarget.length; i++)
+ {
+ var edge = this.connectsAsTarget[i];
+
+ if (edge.maxRank == -1 || edge.maxRank == layer + 1)
+ {
+ // Either edge is not in any rank or
+ // no dummy nodes in edge, add node of other side of edge
+ this.nextLayerConnectedCells[0].push(edge.source);
+ }
+ else
+ {
+ // Edge spans at least two layers, add edge
+ this.nextLayerConnectedCells[0].push(edge);
+ }
+ }
+ }
+
+ return this.nextLayerConnectedCells[0];
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphHierarchyNode.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+ if (this.previousLayerConnectedCells == null)
+ {
+ this.previousLayerConnectedCells = [];
+ this.previousLayerConnectedCells[0] = [];
+
+ for (var i = 0; i < this.connectsAsSource.length; i++)
+ {
+ var edge = this.connectsAsSource[i];
+
+ if (edge.minRank == -1 || edge.minRank == layer - 1)
+ {
+ // No dummy nodes in edge, add node of other side of edge
+ this.previousLayerConnectedCells[0].push(edge.target);
+ }
+ else
+ {
+ // Edge spans at least two layers, add edge
+ this.previousLayerConnectedCells[0].push(edge);
+ }
+ }
+ }
+
+ return this.previousLayerConnectedCells[0];
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns true.
+ */
+mxGraphHierarchyNode.prototype.isVertex = function()
+{
+ return true;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ *
+ * Gets the value of temp for the specified layer
+ */
+mxGraphHierarchyNode.prototype.getGeneralPurposeVariable = function(layer)
+{
+ return this.temp[0];
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ *
+ * Set the value of temp for the specified layer
+ */
+mxGraphHierarchyNode.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+ this.temp[0] = value;
+};
+
+/**
+ * Function: isAncestor
+ */
+mxGraphHierarchyNode.prototype.isAncestor = function(otherNode)
+{
+ // Firstly, the hash code of this node needs to be shorter than the
+ // other node
+ if (otherNode != null && this.hashCode != null && otherNode.hashCode != null
+ && this.hashCode.length < otherNode.hashCode.length)
+ {
+ if (this.hashCode == otherNode.hashCode)
+ {
+ return true;
+ }
+
+ if (this.hashCode == null || this.hashCode == null)
+ {
+ return false;
+ }
+
+ // Secondly, this hash code must match the start of the other
+ // node's hash code. Arrays.equals cannot be used here since
+ // the arrays are different length, and we do not want to
+ // perform another array copy.
+ for (var i = 0; i < this.hashCode.length; i++)
+ {
+ if (this.hashCode[i] != otherNode.hashCode[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: getCoreCell
+ *
+ * Gets the core vertex associated with this wrapper
+ */
+mxGraphHierarchyNode.prototype.getCoreCell = function()
+{
+ return this.cell;
+}; \ No newline at end of file
diff --git a/src/js/layout/hierarchical/mxHierarchicalLayout.js b/src/js/layout/hierarchical/mxHierarchicalLayout.js
new file mode 100644
index 0000000..6ce0e05
--- /dev/null
+++ b/src/js/layout/hierarchical/mxHierarchicalLayout.js
@@ -0,0 +1,623 @@
+/**
+ * $Id: mxHierarchicalLayout.js,v 1.30 2012-12-18 12:41:06 david Exp $
+ * Copyright (c) 2005-2012, JGraph Ltd
+ */
+/**
+ * Class: mxHierarchicalLayout
+ *
+ * A hierarchical layout algorithm.
+ *
+ * Constructor: mxHierarchicalLayout
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * orientation - Optional constant that defines the orientation of this
+ * layout.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxHierarchicalLayout(graph, orientation, deterministic)
+{
+ mxGraphLayout.call(this, graph);
+ this.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;
+ this.deterministic = (deterministic != null) ? deterministic : true;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxHierarchicalLayout.prototype = new mxGraphLayout();
+mxHierarchicalLayout.prototype.constructor = mxHierarchicalLayout;
+
+/**
+ * Variable: roots
+ *
+ * Holds the array of <mxGraphLayouts> that this layout contains.
+ */
+mxHierarchicalLayout.prototype.roots = null;
+
+/**
+ * Variable: resizeParent
+ *
+ * Specifies if the parent should be resized after the layout so that it
+ * contains all the child cells. Default is false. See also <parentBorder>.
+ */
+mxHierarchicalLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: moveParent
+ *
+ * Specifies if the parent should be moved if <resizeParent> is enabled.
+ * Default is false.
+ */
+mxHierarchicalLayout.prototype.moveParent = false;
+
+/**
+ * Variable: parentBorder
+ *
+ * The border to be added around the children if the parent is to be
+ * resized using <resizeParent>. Default is 0.
+ */
+mxHierarchicalLayout.prototype.parentBorder = 0;
+
+/**
+ * Variable: intraCellSpacing
+ *
+ * The spacing buffer added between cells on the same layer. Default is 30.
+ */
+mxHierarchicalLayout.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ *
+ * The spacing buffer added between cell on adjacent layers. Default is 50.
+ */
+mxHierarchicalLayout.prototype.interRankCellSpacing = 50;
+
+/**
+ * Variable: interHierarchySpacing
+ *
+ * The spacing buffer between unconnected hierarchies. Default is 60.
+ */
+mxHierarchicalLayout.prototype.interHierarchySpacing = 60;
+
+/**
+ * Variable: parallelEdgeSpacing
+ *
+ * The distance between each parallel edge on each ranks for long edges
+ */
+mxHierarchicalLayout.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: orientation
+ *
+ * The position of the root node(s) relative to the laid out graph in.
+ * Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxHierarchicalLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: fineTuning
+ *
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxHierarchicalLayout.prototype.fineTuning = true;
+
+/**
+ *
+ * Variable: tightenToSource
+ *
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxHierarchicalLayout.prototype.tightenToSource = true;
+
+/**
+ * Variable: disableEdgeStyle
+ *
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxHierarchicalLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: promoteEdges
+ *
+ * Whether or not to promote edges that terminate on vertices with
+ * different but common ancestry to appear connected to the highest
+ * siblings in the ancestry chains
+ */
+mxHierarchicalLayout.prototype.promoteEdges = true;
+
+/**
+ * Variable: traverseAncestors
+ *
+ * Whether or not to navigate edges whose terminal vertices
+ * have different parents but are in the same ancestry chain
+ */
+mxHierarchicalLayout.prototype.traverseAncestors = true;
+
+/**
+ * Variable: model
+ *
+ * The internal <mxGraphHierarchyModel> formed of the layout.
+ */
+mxHierarchicalLayout.prototype.model = null;
+
+/**
+ * Function: getModel
+ *
+ * Returns the internal <mxGraphHierarchyModel> for this layout algorithm.
+ */
+mxHierarchicalLayout.prototype.getModel = function()
+{
+ return this.model;
+};
+
+/**
+ * Function: execute
+ *
+ * Executes the layout for the children of the specified parent.
+ *
+ * Parameters:
+ *
+ * parent - Parent <mxCell> that contains the children to be laid out.
+ * roots - Optional starting roots of the layout.
+ */
+mxHierarchicalLayout.prototype.execute = function(parent, roots)
+{
+ this.parent = parent;
+ var model = this.graph.model;
+
+ // If the roots are set and the parent is set, only
+ // use the roots that are some dependent of the that
+ // parent.
+ // If just the root are set, use them as-is
+ // If just the parent is set use it's immediate
+ // children as the initial set
+
+ if (roots == null && parent == null)
+ {
+ // TODO indicate the problem
+ return;
+ }
+
+ if (roots != null && parent != null)
+ {
+ var rootsCopy = [];
+
+ for (var i = 0; i < roots.length; i++)
+ {
+
+ if (model.isAncestor(parent, roots[i]))
+ {
+ rootsCopy.push(roots[i]);
+ }
+ }
+
+ this.roots = rootsCopy;
+ }
+ else
+ {
+ this.roots = roots;
+ }
+
+ model.beginUpdate();
+ try
+ {
+ this.run(parent);
+
+ if (this.resizeParent &&
+ !this.graph.isCellCollapsed(parent))
+ {
+ this.graph.updateGroupBounds([parent],
+ this.parentBorder, this.moveParent);
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: findRoots
+ *
+ * Returns all visible children in the given parent which do not have
+ * incoming edges. If the result is empty then the children with the
+ * maximum difference between incoming and outgoing edges are returned.
+ * This takes into account edges that are being promoted to the given
+ * root due to invisible children or collapsed cells.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be checked.
+ * vertices - array of vertices to limit search to
+ */
+mxHierarchicalLayout.prototype.findRoots = function(parent, vertices)
+{
+ var roots = [];
+
+ if (parent != null && vertices != null)
+ {
+ var model = this.graph.model;
+ var best = null;
+ var maxDiff = -100000;
+
+ for (var i in vertices)
+ {
+ var cell = vertices[i];
+
+ if (model.isVertex(cell) && this.graph.isCellVisible(cell))
+ {
+ var conns = this.getEdges(cell);
+ var fanOut = 0;
+ var fanIn = 0;
+
+ for (var k = 0; k < conns.length; k++)
+ {
+ var src = this.graph.view.getVisibleTerminal(conns[k], true);
+
+ if (src == cell)
+ {
+ fanOut++;
+ }
+ else
+ {
+ fanIn++;
+ }
+ }
+
+ if (fanIn == 0 && fanOut > 0)
+ {
+ roots.push(cell);
+ }
+
+ var diff = fanOut - fanIn;
+
+ if (diff > maxDiff)
+ {
+ maxDiff = diff;
+ best = cell;
+ }
+ }
+ }
+
+ if (roots.length == 0 && best != null)
+ {
+ roots.push(best);
+ }
+ }
+
+ return roots;
+};
+
+/**
+ * Function: getEdges
+ *
+ * Returns the connected edges for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose edges should be returned.
+ */
+mxHierarchicalLayout.prototype.getEdges = function(cell)
+{
+ var model = this.graph.model;
+ var edges = [];
+ var isCollapsed = this.graph.isCellCollapsed(cell);
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+
+ if (isCollapsed || !this.graph.isCellVisible(child))
+ {
+ edges = edges.concat(model.getEdges(child, true, true));
+ }
+ }
+
+ edges = edges.concat(model.getEdges(cell, true, true));
+ var result = [];
+
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.graph.view.getState(edges[i]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.graph.view.getVisibleTerminal(edges[i], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.graph.view.getVisibleTerminal(edges[i], false);
+
+ if ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
+ (source == cell && (this.parent == null ||
+ this.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))
+ {
+ result.push(edges[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: run
+ *
+ * The API method used to exercise the layout upon the graph description
+ * and produce a separate description of the vertex position and edge
+ * routing changes made. It runs each stage of the layout that has been
+ * created.
+ */
+mxHierarchicalLayout.prototype.run = function(parent)
+{
+ // Separate out unconnected hierarchies
+ var hierarchyVertices = [];
+ var allVertexSet = [];
+
+ if (this.roots == null && parent != null)
+ {
+ var filledVertexSet = this.filterDescendants(parent);
+
+ this.roots = [];
+ var filledVertexSetEmpty = true;
+
+ // Poor man's isSetEmpty
+ for (var key in filledVertexSet)
+ {
+ if (filledVertexSet[key] != null)
+ {
+ filledVertexSetEmpty = false;
+ break;
+ }
+ }
+
+ while (!filledVertexSetEmpty)
+ {
+ var candidateRoots = this.findRoots(parent, filledVertexSet);
+
+ for (var i = 0; i < candidateRoots.length; i++)
+ {
+ var vertexSet = [];
+ hierarchyVertices.push(vertexSet);
+
+ this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,
+ hierarchyVertices, filledVertexSet);
+ }
+
+ for (var i = 0; i < candidateRoots.length; i++)
+ {
+ this.roots.push(candidateRoots[i]);
+ }
+
+ filledVertexSetEmpty = true;
+
+ // Poor man's isSetEmpty
+ for (var key in filledVertexSet)
+ {
+ if (filledVertexSet[key] != null)
+ {
+ filledVertexSetEmpty = false;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ // Find vertex set as directed traversal from roots
+
+ for (var i = 0; i < roots.length; i++)
+ {
+ var vertexSet = [];
+ hierarchyVertices.push(vertexSet);
+
+ traverse(roots.get(i), true, null, allVertexSet, vertexSet,
+ hierarchyVertices, null);
+ }
+ }
+
+ // Iterate through the result removing parents who have children in this layout
+
+ // Perform a layout for each seperate hierarchy
+ // Track initial coordinate x-positioning
+ var initialX = 0;
+
+ for (var i = 0; i < hierarchyVertices.length; i++)
+ {
+ var vertexSet = hierarchyVertices[i];
+ var tmp = [];
+
+ for (var key in vertexSet)
+ {
+ tmp.push(vertexSet[key]);
+ }
+
+ this.model = new mxGraphHierarchyModel(this, tmp, this.roots,
+ parent, this.tightenToSource);
+
+ this.cycleStage(parent);
+ this.layeringStage();
+
+ this.crossingStage(parent);
+ initialX = this.placementStage(initialX, parent);
+ }
+};
+
+/**
+ * Function: filterDescendants
+ *
+ * Creates an array of descendant cells
+ */
+mxHierarchicalLayout.prototype.filterDescendants = function(cell)
+{
+ var model = this.graph.model;
+ var result = [];
+
+ if (model.isVertex(cell) && cell != this.parent && this.graph.isCellVisible(cell))
+ {
+ result.push(cell);
+ }
+
+ if (this.traverseAncestors || cell == this.parent
+ && this.graph.isCellVisible(cell))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ var children = this.filterDescendants(child);
+
+ for (var j = 0; j < children.length; j++)
+ {
+ result[mxCellPath.create(children[j])] = children[j];
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * allVertices - Array of cell paths for the visited cells.
+ */
+mxHierarchicalLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,
+ hierarchyVertices, filledVertexSet)
+{
+ var view = this.graph.view;
+ var model = this.graph.model;
+
+ if (vertex != null && allVertices != null)
+ {
+ // Has this vertex been seen before in any traversal
+ // And if the filled vertex set is populated, only
+ // process vertices in that it contains
+ var vertexID = mxCellPath.create(vertex);
+
+ if ((allVertices[vertexID] == null)
+ && (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))
+ {
+ if (currentComp[vertexID] == null)
+ {
+ currentComp[vertexID] = vertex;
+ }
+ if (allVertices[vertexID] == null)
+ {
+ allVertices[vertexID] = vertex;
+ }
+
+ delete filledVertexSet[vertexID];
+
+ var edgeCount = model.getEdgeCount(vertex);
+
+ if (edgeCount > 0)
+ {
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var e = model.getEdgeAt(vertex, i);
+ var isSource = view.getVisibleTerminal(e, true) == vertex;
+
+ if (!directed || isSource)
+ {
+ var next = view.getVisibleTerminal(e, !isSource);
+ currentComp = this.traverse(next, directed, e, allVertices,
+ currentComp, hierarchyVertices,
+ filledVertexSet);
+ }
+ }
+ }
+ }
+ else
+ {
+ if (currentComp[vertexID] == null)
+ {
+ // We've seen this vertex before, but not in the current component
+ // This component and the one it's in need to be merged
+
+ for (var i = 0; i < hierarchyVertices.length; i++)
+ {
+ var comp = hierarchyVertices[i];
+
+ if (comp[vertexID] != null)
+ {
+ for (var key in currentComp)
+ {
+ comp[key] = currentComp[key];
+ }
+
+ // Remove the current component from the hierarchy set
+ hierarchyVertices.pop();
+ return comp;
+ }
+ }
+ }
+ }
+ }
+
+ return currentComp;
+};
+
+/**
+ * Function: cycleStage
+ *
+ * Executes the cycle stage using mxMinimumCycleRemover.
+ */
+mxHierarchicalLayout.prototype.cycleStage = function(parent)
+{
+ var cycleStage = new mxMinimumCycleRemover(this);
+ cycleStage.execute(parent);
+};
+
+/**
+ * Function: layeringStage
+ *
+ * Implements first stage of a Sugiyama layout.
+ */
+mxHierarchicalLayout.prototype.layeringStage = function()
+{
+ this.model.initialRank();
+ this.model.fixRanks();
+};
+
+/**
+ * Function: crossingStage
+ *
+ * Executes the crossing stage using mxMedianHybridCrossingReduction.
+ */
+mxHierarchicalLayout.prototype.crossingStage = function(parent)
+{
+ var crossingStage = new mxMedianHybridCrossingReduction(this);
+ crossingStage.execute(parent);
+};
+
+/**
+ * Function: placementStage
+ *
+ * Executes the placement stage using mxCoordinateAssignment.
+ */
+mxHierarchicalLayout.prototype.placementStage = function(initialX, parent)
+{
+ var placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,
+ this.interRankCellSpacing, this.orientation, initialX,
+ this.parallelEdgeSpacing);
+ placementStage.fineTuning = this.fineTuning;
+ placementStage.execute(parent);
+
+ return placementStage.limitX + this.interHierarchySpacing;
+};
diff --git a/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js b/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js
new file mode 100644
index 0000000..8b73ccf
--- /dev/null
+++ b/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js
@@ -0,0 +1,1836 @@
+/**
+ * $Id: mxCoordinateAssignment.js,v 1.29 2012-06-21 14:28:09 david Exp $
+ * Copyright (c) 2005-2012, JGraph Ltd
+ */
+/**
+ * Class: mxCoordinateAssignment
+ *
+ * Sets the horizontal locations of node and edge dummy nodes on each layer.
+ * Uses median down and up weighings as well as heuristics to straighten edges as
+ * far as possible.
+ *
+ * Constructor: mxCoordinateAssignment
+ *
+ * Creates a coordinate assignment.
+ *
+ * Arguments:
+ *
+ * intraCellSpacing - the minimum buffer between cells on the same rank
+ * interRankCellSpacing - the minimum distance between cells on adjacent ranks
+ * orientation - the position of the root node(s) relative to the graph
+ * initialX - the leftmost coordinate node placement starts at
+ */
+function mxCoordinateAssignment(layout, intraCellSpacing, interRankCellSpacing,
+ orientation, initialX, parallelEdgeSpacing)
+{
+ this.layout = layout;
+ this.intraCellSpacing = intraCellSpacing;
+ this.interRankCellSpacing = interRankCellSpacing;
+ this.orientation = orientation;
+ this.initialX = initialX;
+ this.parallelEdgeSpacing = parallelEdgeSpacing;
+};
+
+var mxHierarchicalEdgeStyle =
+{
+ ORTHOGONAL: 1,
+ POLYLINE: 2,
+ STRAIGHT: 3
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxCoordinateAssignment.prototype = new mxHierarchicalLayoutStage();
+mxCoordinateAssignment.prototype.constructor = mxCoordinateAssignment;
+
+/**
+ * Variable: layout
+ *
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxCoordinateAssignment.prototype.layout = null;
+
+/**
+ * Variable: intraCellSpacing
+ *
+ * The minimum buffer between cells on the same rank. Default is 30.
+ */
+mxCoordinateAssignment.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ *
+ * The minimum distance between cells on adjacent ranks. Default is 10.
+ */
+mxCoordinateAssignment.prototype.interRankCellSpacing = 10;
+
+/**
+ * Variable: parallelEdgeSpacing
+ *
+ * The distance between each parallel edge on each ranks for long edges.
+ * Default is 10.
+ */
+mxCoordinateAssignment.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: maxIterations
+ *
+ * The number of heuristic iterations to run. Default is 8.
+ */
+mxCoordinateAssignment.prototype.maxIterations = 8;
+
+/**
+ * Variable: prefHozEdgeSep
+ *
+ * The preferred horizontal distance between edges exiting a vertex
+ */
+mxCoordinateAssignment.prototype.prefHozEdgeSep = 5;
+
+/**
+ * Variable: prefVertEdgeOff
+ *
+ * The preferred vertical offset between edges exiting a vertex
+ */
+mxCoordinateAssignment.prototype.prefVertEdgeOff = 2;
+
+/**
+ * Variable: minEdgeJetty
+ *
+ * The minimum distance for an edge jetty from a vertex
+ */
+mxCoordinateAssignment.prototype.minEdgeJetty = 12;
+
+/**
+ * Variable: channelBuffer
+ *
+ * The size of the vertical buffer in the center of inter-rank channels
+ * where edge control points should not be placed
+ */
+mxCoordinateAssignment.prototype.channelBuffer = 4;
+
+/**
+ * Variable: jettyPositions
+ *
+ * Map of internal edges and (x,y) pair of positions of the start and end jetty
+ * for that edge where it connects to the source and target vertices.
+ * Note this should technically be a WeakHashMap, but since JS does not
+ * have an equivalent, housekeeping must be performed before using.
+ * i.e. check all edges are still in the model and clear the values.
+ * Note that the y co-ord is the offset of the jetty, not the
+ * absolute point
+ */
+mxCoordinateAssignment.prototype.jettyPositions = null;
+
+/**
+ * Variable: orientation
+ *
+ * The position of the root ( start ) node(s) relative to the rest of the
+ * laid out graph. Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxCoordinateAssignment.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: initialX
+ *
+ * The minimum x position node placement starts at
+ */
+mxCoordinateAssignment.prototype.initialX = null;
+
+/**
+ * Variable: limitX
+ *
+ * The maximum x value this positioning lays up to
+ */
+mxCoordinateAssignment.prototype.limitX = null;
+
+/**
+ * Variable: currentXDelta
+ *
+ * The sum of x-displacements for the current iteration
+ */
+mxCoordinateAssignment.prototype.currentXDelta = null;
+
+/**
+ * Variable: widestRank
+ *
+ * The rank that has the widest x position
+ */
+mxCoordinateAssignment.prototype.widestRank = null;
+
+/**
+ * Variable: rankTopY
+ *
+ * Internal cache of top-most values of Y for each rank
+ */
+mxCoordinateAssignment.prototype.rankTopY = null;
+
+/**
+ * Variable: rankBottomY
+ *
+ * Internal cache of bottom-most value of Y for each rank
+ */
+mxCoordinateAssignment.prototype.rankBottomY = null;
+
+/**
+ * Variable: widestRankValue
+ *
+ * The X-coordinate of the edge of the widest rank
+ */
+mxCoordinateAssignment.prototype.widestRankValue = null;
+
+/**
+ * Variable: rankWidths
+ *
+ * The width of all the ranks
+ */
+mxCoordinateAssignment.prototype.rankWidths = null;
+
+/**
+ * Variable: rankY
+ *
+ * The Y-coordinate of all the ranks
+ */
+mxCoordinateAssignment.prototype.rankY = null;
+
+/**
+ * Variable: fineTuning
+ *
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxCoordinateAssignment.prototype.fineTuning = true;
+
+/**
+ * Variable: edgeStyle
+ *
+ * The style to apply between cell layers to edge segments
+ */
+mxCoordinateAssignment.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
+
+/**
+ * Variable: nextLayerConnectedCache
+ *
+ * A store of connections to the layer above for speed
+ */
+mxCoordinateAssignment.prototype.nextLayerConnectedCache = null;
+
+/**
+ * Variable: previousLayerConnectedCache
+ *
+ * A store of connections to the layer below for speed
+ */
+mxCoordinateAssignment.prototype.previousLayerConnectedCache = null;
+
+/**
+ * Variable: groupPadding
+ *
+ * Padding added to resized parents
+ */
+mxCoordinateAssignment.prototype.groupPadding = 10;
+
+/**
+ * Utility method to display current positions
+ */
+mxCoordinateAssignment.prototype.printStatus = function()
+{
+ var model = this.layout.getModel();
+ mxLog.show();
+
+ mxLog.writeln('======Coord assignment debug=======');
+
+ for (var j = 0; j < model.ranks.length; j++)
+ {
+ mxLog.write('Rank ', j, ' : ' );
+ var rank = model.ranks[j];
+
+ for (var k = 0; k < rank.length; k++)
+ {
+ var cell = rank[k];
+
+ mxLog.write(cell.getGeneralPurposeVariable(j), ' ');
+ }
+ mxLog.writeln();
+ }
+
+ mxLog.writeln('====================================');
+};
+
+/**
+ * Function: execute
+ *
+ * A basic horizontal coordinate assignment algorithm
+ */
+mxCoordinateAssignment.prototype.execute = function(parent)
+{
+ this.jettyPositions = [];
+ var model = this.layout.getModel();
+ this.currentXDelta = 0.0;
+
+ this.initialCoords(this.layout.getGraph(), model);
+
+// this.printStatus();
+
+ if (this.fineTuning)
+ {
+ this.minNode(model);
+ }
+
+ var bestXDelta = 100000000.0;
+
+ if (this.fineTuning)
+ {
+ for (var i = 0; i < this.maxIterations; i++)
+ {
+// this.printStatus();
+
+ // Median Heuristic
+ if (i != 0)
+ {
+ this.medianPos(i, model);
+ this.minNode(model);
+ }
+
+ // if the total offset is less for the current positioning,
+ // there are less heavily angled edges and so the current
+ // positioning is used
+ if (this.currentXDelta < bestXDelta)
+ {
+ for (var j = 0; j < model.ranks.length; j++)
+ {
+ var rank = model.ranks[j];
+
+ for (var k = 0; k < rank.length; k++)
+ {
+ var cell = rank[k];
+ cell.setX(j, cell.getGeneralPurposeVariable(j));
+ }
+ }
+
+ bestXDelta = this.currentXDelta;
+ }
+ else
+ {
+ // Restore the best positions
+ for (var j = 0; j < model.ranks.length; j++)
+ {
+ var rank = model.ranks[j];
+
+ for (var k = 0; k < rank.length; k++)
+ {
+ var cell = rank[k];
+ cell.setGeneralPurposeVariable(j, cell.getX(j));
+ }
+ }
+ }
+
+ this.minPath(this.layout.getGraph(), model);
+
+ this.currentXDelta = 0;
+ }
+ }
+
+ this.setCellLocations(this.layout.getGraph(), model);
+};
+
+/**
+ * Function: minNode
+ *
+ * Performs one median positioning sweep in both directions
+ */
+mxCoordinateAssignment.prototype.minNode = function(model)
+{
+ // Queue all nodes
+ var nodeList = [];
+
+ // Need to be able to map from cell to cellWrapper
+ var map = [];
+ var rank = [];
+
+ for (var i = 0; i <= model.maxRank; i++)
+ {
+ rank[i] = model.ranks[i];
+
+ for (var j = 0; j < rank[i].length; j++)
+ {
+ // Use the weight to store the rank and visited to store whether
+ // or not the cell is in the list
+ var node = rank[i][j];
+ var nodeWrapper = new WeightedCellSorter(node, i);
+ nodeWrapper.rankIndex = j;
+ nodeWrapper.visited = true;
+ nodeList.push(nodeWrapper);
+
+ var cellId = mxCellPath.create(node.getCoreCell());
+ map[cellId] = nodeWrapper;
+ }
+ }
+
+ // Set a limit of the maximum number of times we will access the queue
+ // in case a loop appears
+ var maxTries = nodeList.length * 10;
+ var count = 0;
+
+ // Don't move cell within this value of their median
+ var tolerance = 1;
+
+ while (nodeList.length > 0 && count <= maxTries)
+ {
+ var cellWrapper = nodeList.shift();
+ var cell = cellWrapper.cell;
+
+ var rankValue = cellWrapper.weightedValue;
+ var rankIndex = parseInt(cellWrapper.rankIndex);
+
+ var nextLayerConnectedCells = cell.getNextLayerConnectedCells(rankValue);
+ var previousLayerConnectedCells = cell.getPreviousLayerConnectedCells(rankValue);
+
+ var numNextLayerConnected = nextLayerConnectedCells.length;
+ var numPreviousLayerConnected = previousLayerConnectedCells.length;
+
+ var medianNextLevel = this.medianXValue(nextLayerConnectedCells,
+ rankValue + 1);
+ var medianPreviousLevel = this.medianXValue(previousLayerConnectedCells,
+ rankValue - 1);
+
+ var numConnectedNeighbours = numNextLayerConnected
+ + numPreviousLayerConnected;
+ var currentPosition = cell.getGeneralPurposeVariable(rankValue);
+ var cellMedian = currentPosition;
+
+ if (numConnectedNeighbours > 0)
+ {
+ cellMedian = (medianNextLevel * numNextLayerConnected + medianPreviousLevel
+ * numPreviousLayerConnected)
+ / numConnectedNeighbours;
+ }
+
+ // Flag storing whether or not position has changed
+ var positionChanged = false;
+
+ if (cellMedian < currentPosition - tolerance)
+ {
+ if (rankIndex == 0)
+ {
+ cell.setGeneralPurposeVariable(rankValue, cellMedian);
+ positionChanged = true;
+ }
+ else
+ {
+ var leftCell = rank[rankValue][rankIndex - 1];
+ var leftLimit = leftCell
+ .getGeneralPurposeVariable(rankValue);
+ leftLimit = leftLimit + leftCell.width / 2
+ + this.intraCellSpacing + cell.width / 2;
+
+ if (leftLimit < cellMedian)
+ {
+ cell.setGeneralPurposeVariable(rankValue, cellMedian);
+ positionChanged = true;
+ }
+ else if (leftLimit < cell
+ .getGeneralPurposeVariable(rankValue)
+ - tolerance)
+ {
+ cell.setGeneralPurposeVariable(rankValue, leftLimit);
+ positionChanged = true;
+ }
+ }
+ }
+ else if (cellMedian > currentPosition + tolerance)
+ {
+ var rankSize = rank[rankValue].length;
+
+ if (rankIndex == rankSize - 1)
+ {
+ cell.setGeneralPurposeVariable(rankValue, cellMedian);
+ positionChanged = true;
+ }
+ else
+ {
+ var rightCell = rank[rankValue][rankIndex + 1];
+ var rightLimit = rightCell
+ .getGeneralPurposeVariable(rankValue);
+ rightLimit = rightLimit - rightCell.width / 2
+ - this.intraCellSpacing - cell.width / 2;
+
+ if (rightLimit > cellMedian)
+ {
+ cell.setGeneralPurposeVariable(rankValue, cellMedian);
+ positionChanged = true;
+ }
+ else if (rightLimit > cell
+ .getGeneralPurposeVariable(rankValue)
+ + tolerance)
+ {
+ cell.setGeneralPurposeVariable(rankValue, rightLimit);
+ positionChanged = true;
+ }
+ }
+ }
+
+ if (positionChanged)
+ {
+ // Add connected nodes to map and list
+ for (var i = 0; i < nextLayerConnectedCells.length; i++)
+ {
+ var connectedCell = nextLayerConnectedCells[i];
+ var connectedCellId = mxCellPath.create(connectedCell.getCoreCell());
+ var connectedCellWrapper = map[connectedCellId];
+
+ if (connectedCellWrapper != null)
+ {
+ if (connectedCellWrapper.visited == false)
+ {
+ connectedCellWrapper.visited = true;
+ nodeList.push(connectedCellWrapper);
+ }
+ }
+ }
+
+ // Add connected nodes to map and list
+ for (var i = 0; i < previousLayerConnectedCells.length; i++)
+ {
+ var connectedCell = previousLayerConnectedCells[i];
+ var connectedCellId = mxCellPath.create(connectedCell.getCoreCell());
+ var connectedCellWrapper = map[connectedCellId];
+
+ if (connectedCellWrapper != null)
+ {
+ if (connectedCellWrapper.visited == false)
+ {
+ connectedCellWrapper.visited = true;
+ nodeList.push(connectedCellWrapper);
+ }
+ }
+ }
+ }
+
+ cellWrapper.visited = false;
+ count++;
+ }
+};
+
+/**
+ * Function: medianPos
+ *
+ * Performs one median positioning sweep in one direction
+ *
+ * Parameters:
+ *
+ * i - the iteration of the whole process
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.medianPos = function(i, model)
+{
+ // Reverse sweep direction each time through this method
+ var downwardSweep = (i % 2 == 0);
+
+ if (downwardSweep)
+ {
+ for (var j = model.maxRank; j > 0; j--)
+ {
+ this.rankMedianPosition(j - 1, model, j);
+ }
+ }
+ else
+ {
+ for (var j = 0; j < model.maxRank - 1; j++)
+ {
+ this.rankMedianPosition(j + 1, model, j);
+ }
+ }
+};
+
+/**
+ * Function: rankMedianPosition
+ *
+ * Performs median minimisation over one rank.
+ *
+ * Parameters:
+ *
+ * rankValue - the layer number of this rank
+ * model - an internal model of the hierarchical layout
+ * nextRankValue - the layer number whose connected cels are to be laid out
+ * relative to
+ */
+mxCoordinateAssignment.prototype.rankMedianPosition = function(rankValue, model, nextRankValue)
+{
+ var rank = model.ranks[rankValue];
+
+ // Form an array of the order in which the cell are to be processed
+ // , the order is given by the weighted sum of the in or out edges,
+ // depending on whether we're travelling up or down the hierarchy.
+ var weightedValues = [];
+ var cellMap = [];
+
+ for (var i = 0; i < rank.length; i++)
+ {
+ var currentCell = rank[i];
+ weightedValues[i] = new WeightedCellSorter();
+ weightedValues[i].cell = currentCell;
+ weightedValues[i].rankIndex = i;
+ var currentCellId = mxCellPath.create(currentCell.getCoreCell());
+ cellMap[currentCellId] = weightedValues[i];
+ var nextLayerConnectedCells = null;
+
+ if (nextRankValue < rankValue)
+ {
+ nextLayerConnectedCells = currentCell
+ .getPreviousLayerConnectedCells(rankValue);
+ }
+ else
+ {
+ nextLayerConnectedCells = currentCell
+ .getNextLayerConnectedCells(rankValue);
+ }
+
+ // Calculate the weighing based on this node type and those this
+ // node is connected to on the next layer
+ weightedValues[i].weightedValue = this.calculatedWeightedValue(
+ currentCell, nextLayerConnectedCells);
+ }
+
+ weightedValues.sort(WeightedCellSorter.prototype.compare);
+
+ // Set the new position of each node within the rank using
+ // its temp variable
+
+ for (var i = 0; i < weightedValues.length; i++)
+ {
+ var numConnectionsNextLevel = 0;
+ var cell = weightedValues[i].cell;
+ var nextLayerConnectedCells = null;
+ var medianNextLevel = 0;
+
+ if (nextRankValue < rankValue)
+ {
+ nextLayerConnectedCells = cell.getPreviousLayerConnectedCells(
+ rankValue).slice();
+ }
+ else
+ {
+ nextLayerConnectedCells = cell.getNextLayerConnectedCells(
+ rankValue).slice();
+ }
+
+ if (nextLayerConnectedCells != null)
+ {
+ numConnectionsNextLevel = nextLayerConnectedCells.length;
+
+ if (numConnectionsNextLevel > 0)
+ {
+ medianNextLevel = this.medianXValue(nextLayerConnectedCells,
+ nextRankValue);
+ }
+ else
+ {
+ // For case of no connections on the next level set the
+ // median to be the current position and try to be
+ // positioned there
+ medianNextLevel = cell.getGeneralPurposeVariable(rankValue);
+ }
+ }
+
+ var leftBuffer = 0.0;
+ var leftLimit = -100000000.0;
+
+ for (var j = weightedValues[i].rankIndex - 1; j >= 0;)
+ {
+ var rankId = mxCellPath.create(rank[j].getCoreCell());
+ var weightedValue = cellMap[rankId];
+
+ if (weightedValue != null)
+ {
+ var leftCell = weightedValue.cell;
+
+ if (weightedValue.visited)
+ {
+ // The left limit is the right hand limit of that
+ // cell plus any allowance for unallocated cells
+ // in-between
+ leftLimit = leftCell
+ .getGeneralPurposeVariable(rankValue)
+ + leftCell.width
+ / 2.0
+ + this.intraCellSpacing
+ + leftBuffer + cell.width / 2.0;
+ j = -1;
+ }
+ else
+ {
+ leftBuffer += leftCell.width + this.intraCellSpacing;
+ j--;
+ }
+ }
+ }
+
+ var rightBuffer = 0.0;
+ var rightLimit = 100000000.0;
+
+ for (var j = weightedValues[i].rankIndex + 1; j < weightedValues.length;)
+ {
+ var rankId = mxCellPath.create(rank[j].getCoreCell());
+ var weightedValue = cellMap[rankId];
+
+ if (weightedValue != null)
+ {
+ var rightCell = weightedValue.cell;
+
+ if (weightedValue.visited)
+ {
+ // The left limit is the right hand limit of that
+ // cell plus any allowance for unallocated cells
+ // in-between
+ rightLimit = rightCell
+ .getGeneralPurposeVariable(rankValue)
+ - rightCell.width
+ / 2.0
+ - this.intraCellSpacing
+ - rightBuffer - cell.width / 2.0;
+ j = weightedValues.length;
+ }
+ else
+ {
+ rightBuffer += rightCell.width + this.intraCellSpacing;
+ j++;
+ }
+ }
+ }
+
+ if (medianNextLevel >= leftLimit && medianNextLevel <= rightLimit)
+ {
+ cell.setGeneralPurposeVariable(rankValue, medianNextLevel);
+ }
+ else if (medianNextLevel < leftLimit)
+ {
+ // Couldn't place at median value, place as close to that
+ // value as possible
+ cell.setGeneralPurposeVariable(rankValue, leftLimit);
+ this.currentXDelta += leftLimit - medianNextLevel;
+ }
+ else if (medianNextLevel > rightLimit)
+ {
+ // Couldn't place at median value, place as close to that
+ // value as possible
+ cell.setGeneralPurposeVariable(rankValue, rightLimit);
+ this.currentXDelta += medianNextLevel - rightLimit;
+ }
+
+ weightedValues[i].visited = true;
+ }
+};
+
+/**
+ * Function: calculatedWeightedValue
+ *
+ * Calculates the priority the specified cell has based on the type of its
+ * cell and the cells it is connected to on the next layer
+ *
+ * Parameters:
+ *
+ * currentCell - the cell whose weight is to be calculated
+ * collection - the cells the specified cell is connected to
+ */
+mxCoordinateAssignment.prototype.calculatedWeightedValue = function(currentCell, collection)
+{
+ var totalWeight = 0;
+
+ for (var i = 0; i < collection.length; i++)
+ {
+ var cell = collection[i];
+
+ if (currentCell.isVertex() && cell.isVertex())
+ {
+ totalWeight++;
+ }
+ else if (currentCell.isEdge() && cell.isEdge())
+ {
+ totalWeight += 8;
+ }
+ else
+ {
+ totalWeight += 2;
+ }
+ }
+
+ return totalWeight;
+};
+
+/**
+ * Function: medianXValue
+ *
+ * Calculates the median position of the connected cell on the specified
+ * rank
+ *
+ * Parameters:
+ *
+ * connectedCells - the cells the candidate connects to on this level
+ * rankValue - the layer number of this rank
+ */
+mxCoordinateAssignment.prototype.medianXValue = function(connectedCells, rankValue)
+{
+ if (connectedCells.length == 0)
+ {
+ return 0;
+ }
+
+ var medianValues = [];
+
+ for (var i = 0; i < connectedCells.length; i++)
+ {
+ medianValues[i] = connectedCells[i].getGeneralPurposeVariable(rankValue);
+ }
+
+ medianValues.sort(function(a,b){return a - b;});
+
+ if (connectedCells.length % 2 == 1)
+ {
+ // For odd numbers of adjacent vertices return the median
+ return medianValues[Math.floor(connectedCells.length / 2)];
+ }
+ else
+ {
+ var medianPoint = connectedCells.length / 2;
+ var leftMedian = medianValues[medianPoint - 1];
+ var rightMedian = medianValues[medianPoint];
+
+ return ((leftMedian + rightMedian) / 2);
+ }
+};
+
+/**
+ * Function: initialCoords
+ *
+ * Sets up the layout in an initial positioning. The ranks are all centered
+ * as much as possible along the middle vertex in each rank. The other cells
+ * are then placed as close as possible on either side.
+ *
+ * Parameters:
+ *
+ * facade - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.initialCoords = function(facade, model)
+{
+ this.calculateWidestRank(facade, model);
+
+ // Sweep up and down from the widest rank
+ for (var i = this.widestRank; i >= 0; i--)
+ {
+ if (i < model.maxRank)
+ {
+ this.rankCoordinates(i, facade, model);
+ }
+ }
+
+ for (var i = this.widestRank+1; i <= model.maxRank; i++)
+ {
+ if (i > 0)
+ {
+ this.rankCoordinates(i, facade, model);
+ }
+ }
+};
+
+/**
+ * Function: rankCoordinates
+ *
+ * Sets up the layout in an initial positioning. All the first cells in each
+ * rank are moved to the left and the rest of the rank inserted as close
+ * together as their size and buffering permits. This method works on just
+ * the specified rank.
+ *
+ * Parameters:
+ *
+ * rankValue - the current rank being processed
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.rankCoordinates = function(rankValue, graph, model)
+{
+ var rank = model.ranks[rankValue];
+ var maxY = 0.0;
+ var localX = this.initialX + (this.widestRankValue - this.rankWidths[rankValue])
+ / 2;
+
+ // Store whether or not any of the cells' bounds were unavailable so
+ // to only issue the warning once for all cells
+ var boundsWarning = false;
+
+ for (var i = 0; i < rank.length; i++)
+ {
+ var node = rank[i];
+
+ if (node.isVertex())
+ {
+ var bounds = this.layout.getVertexBounds(node.cell);
+
+ if (bounds != null)
+ {
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ node.width = bounds.width;
+ node.height = bounds.height;
+ }
+ else
+ {
+ node.width = bounds.height;
+ node.height = bounds.width;
+ }
+ }
+ else
+ {
+ boundsWarning = true;
+ }
+
+ maxY = Math.max(maxY, node.height);
+ }
+ else if (node.isEdge())
+ {
+ // The width is the number of additional parallel edges
+ // time the parallel edge spacing
+ var numEdges = 1;
+
+ if (node.edges != null)
+ {
+ numEdges = node.edges.length;
+ }
+ else
+ {
+ mxLog.warn('edge.edges is null');
+ }
+
+ node.width = (numEdges - 1) * this.parallelEdgeSpacing;
+ }
+
+ // Set the initial x-value as being the best result so far
+ localX += node.width / 2.0;
+ node.setX(rankValue, localX);
+ node.setGeneralPurposeVariable(rankValue, localX);
+ localX += node.width / 2.0;
+ localX += this.intraCellSpacing;
+ }
+
+ if (boundsWarning == true)
+ {
+ mxLog.warn('At least one cell has no bounds');
+ }
+};
+
+/**
+ * Function: calculateWidestRank
+ *
+ * Calculates the width rank in the hierarchy. Also set the y value of each
+ * rank whilst performing the calculation
+ *
+ * Parameters:
+ *
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.calculateWidestRank = function(graph, model)
+{
+ // Starting y co-ordinate
+ var y = -this.interRankCellSpacing;
+
+ // Track the widest cell on the last rank since the y
+ // difference depends on it
+ var lastRankMaxCellHeight = 0.0;
+ this.rankWidths = [];
+ this.rankY = [];
+
+ for (var rankValue = model.maxRank; rankValue >= 0; rankValue--)
+ {
+ // Keep track of the widest cell on this rank
+ var maxCellHeight = 0.0;
+ var rank = model.ranks[rankValue];
+ var localX = this.initialX;
+
+ // Store whether or not any of the cells' bounds were unavailable so
+ // to only issue the warning once for all cells
+ var boundsWarning = false;
+
+ for (var i = 0; i < rank.length; i++)
+ {
+ var node = rank[i];
+
+ if (node.isVertex())
+ {
+ var bounds = this.layout.getVertexBounds(node.cell);
+
+ if (bounds != null)
+ {
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ node.width = bounds.width;
+ node.height = bounds.height;
+ }
+ else
+ {
+ node.width = bounds.height;
+ node.height = bounds.width;
+ }
+ }
+ else
+ {
+ boundsWarning = true;
+ }
+
+ maxCellHeight = Math.max(maxCellHeight, node.height);
+ }
+ else if (node.isEdge())
+ {
+ // The width is the number of additional parallel edges
+ // time the parallel edge spacing
+ var numEdges = 1;
+
+ if (node.edges != null)
+ {
+ numEdges = node.edges.length;
+ }
+ else
+ {
+ mxLog.warn('edge.edges is null');
+ }
+
+ node.width = (numEdges - 1) * this.parallelEdgeSpacing;
+ }
+
+ // Set the initial x-value as being the best result so far
+ localX += node.width / 2.0;
+ node.setX(rankValue, localX);
+ node.setGeneralPurposeVariable(rankValue, localX);
+ localX += node.width / 2.0;
+ localX += this.intraCellSpacing;
+
+ if (localX > this.widestRankValue)
+ {
+ this.widestRankValue = localX;
+ this.widestRank = rankValue;
+ }
+
+ this.rankWidths[rankValue] = localX;
+ }
+
+ if (boundsWarning == true)
+ {
+ mxLog.warn('At least one cell has no bounds');
+ }
+
+ this.rankY[rankValue] = y;
+ var distanceToNextRank = maxCellHeight / 2.0
+ + lastRankMaxCellHeight / 2.0 + this.interRankCellSpacing;
+ lastRankMaxCellHeight = maxCellHeight;
+
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_WEST)
+ {
+ y += distanceToNextRank;
+ }
+ else
+ {
+ y -= distanceToNextRank;
+ }
+
+ for (var i = 0; i < rank.length; i++)
+ {
+ var cell = rank[i];
+ cell.setY(rankValue, y);
+ }
+ }
+};
+
+/**
+ * Function: minPath
+ *
+ * Straightens out chains of virtual nodes where possibleacade to those stored after this layout
+ * processing step has completed.
+ *
+ * Parameters:
+ *
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.minPath = function(graph, model)
+{
+ // Work down and up each edge with at least 2 control points
+ // trying to straighten each one out. If the same number of
+ // straight segments are formed in both directions, the
+ // preferred direction used is the one where the final
+ // control points have the least offset from the connectable
+ // region of the terminating vertices
+ var edges = model.edgeMapper;
+
+ for (var key in edges)
+ {
+ var cell = edges[key];
+
+ if (cell.maxRank - cell.minRank - 1 < 1)
+ {
+ continue;
+ }
+
+ // At least two virtual nodes in the edge
+ // Check first whether the edge is already straight
+ var referenceX = cell
+ .getGeneralPurposeVariable(cell.minRank + 1);
+ var edgeStraight = true;
+ var refSegCount = 0;
+
+ for (var i = cell.minRank + 2; i < cell.maxRank; i++)
+ {
+ var x = cell.getGeneralPurposeVariable(i);
+
+ if (referenceX != x)
+ {
+ edgeStraight = false;
+ referenceX = x;
+ }
+ else
+ {
+ refSegCount++;
+ }
+ }
+
+ if (!edgeStraight)
+ {
+ var upSegCount = 0;
+ var downSegCount = 0;
+ var upXPositions = [];
+ var downXPositions = [];
+
+ var currentX = cell.getGeneralPurposeVariable(cell.minRank + 1);
+
+ for (var i = cell.minRank + 1; i < cell.maxRank - 1; i++)
+ {
+ // Attempt to straight out the control point on the
+ // next segment up with the current control point.
+ var nextX = cell.getX(i + 1);
+
+ if (currentX == nextX)
+ {
+ upXPositions[i - cell.minRank - 1] = currentX;
+ upSegCount++;
+ }
+ else if (this.repositionValid(model, cell, i + 1, currentX))
+ {
+ upXPositions[i - cell.minRank - 1] = currentX;
+ upSegCount++;
+ // Leave currentX at same value
+ }
+ else
+ {
+ upXPositions[i - cell.minRank - 1] = nextX;
+ currentX = nextX;
+ }
+ }
+
+ currentX = cell.getX(i);
+
+ for (var i = cell.maxRank - 1; i > cell.minRank + 1; i--)
+ {
+ // Attempt to straight out the control point on the
+ // next segment down with the current control point.
+ var nextX = cell.getX(i - 1);
+
+ if (currentX == nextX)
+ {
+ downXPositions[i - cell.minRank - 2] = currentX;
+ downSegCount++;
+ }
+ else if (this.repositionValid(model, cell, i - 1, currentX))
+ {
+ downXPositions[i - cell.minRank - 2] = currentX;
+ downSegCount++;
+ // Leave currentX at same value
+ }
+ else
+ {
+ downXPositions[i - cell.minRank - 2] = cell.getX(i-1);
+ currentX = nextX;
+ }
+ }
+
+ if (downSegCount > refSegCount || upSegCount > refSegCount)
+ {
+ if (downSegCount >= upSegCount)
+ {
+ // Apply down calculation values
+ for (var i = cell.maxRank - 2; i > cell.minRank; i--)
+ {
+ cell.setX(i, downXPositions[i - cell.minRank - 1]);
+ }
+ }
+ else if (upSegCount > downSegCount)
+ {
+ // Apply up calculation values
+ for (var i = cell.minRank + 2; i < cell.maxRank; i++)
+ {
+ cell.setX(i, upXPositions[i - cell.minRank - 2]);
+ }
+ }
+ else
+ {
+ // Neither direction provided a favourable result
+ // But both calculations are better than the
+ // existing solution, so apply the one with minimal
+ // offset to attached vertices at either end.
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: repositionValid
+ *
+ * Determines whether or not a node may be moved to the specified x
+ * position on the specified rank
+ *
+ * Parameters:
+ *
+ * model - the layout model
+ * cell - the cell being analysed
+ * rank - the layer of the cell
+ * position - the x position being sought
+ */
+mxCoordinateAssignment.prototype.repositionValid = function(model, cell, rank, position)
+{
+ var rankArray = model.ranks[rank];
+ var rankIndex = -1;
+
+ for (var i = 0; i < rankArray.length; i++)
+ {
+ if (cell == rankArray[i])
+ {
+ rankIndex = i;
+ break;
+ }
+ }
+
+ if (rankIndex < 0)
+ {
+ return false;
+ }
+
+ var currentX = cell.getGeneralPurposeVariable(rank);
+
+ if (position < currentX)
+ {
+ // Trying to move node to the left.
+ if (rankIndex == 0)
+ {
+ // Left-most node, can move anywhere
+ return true;
+ }
+
+ var leftCell = rankArray[rankIndex - 1];
+ var leftLimit = leftCell.getGeneralPurposeVariable(rank);
+ leftLimit = leftLimit + leftCell.width / 2
+ + this.intraCellSpacing + cell.width / 2;
+
+ if (leftLimit <= position)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (position > currentX)
+ {
+ // Trying to move node to the right.
+ if (rankIndex == rankArray.length - 1)
+ {
+ // Right-most node, can move anywhere
+ return true;
+ }
+
+ var rightCell = rankArray[rankIndex + 1];
+ var rightLimit = rightCell.getGeneralPurposeVariable(rank);
+ rightLimit = rightLimit - rightCell.width / 2
+ - this.intraCellSpacing - cell.width / 2;
+
+ if (rightLimit >= position)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+/**
+ * Function: setCellLocations
+ *
+ * Sets the cell locations in the facade to those stored after this layout
+ * processing step has completed.
+ *
+ * Parameters:
+ *
+ * graph - the input graph
+ * model - the layout model
+ */
+mxCoordinateAssignment.prototype.setCellLocations = function(graph, model)
+{
+ this.rankTopY = [];
+ this.rankBottomY = [];
+
+ for (var i = 0; i < model.ranks.length; i++)
+ {
+ this.rankTopY[i] = Number.MAX_VALUE;
+ this.rankBottomY[i] = 0.0;
+ }
+
+ var parentsChanged = null;
+
+ if (this.layout.resizeParent)
+ {
+ parentsChanged = new Object();
+ }
+
+ var edges = model.edgeMapper;
+ var vertices = model.vertexMapper;
+
+ // Process vertices all first, since they define the lower and
+ // limits of each rank. Between these limits lie the channels
+ // where the edges can be routed across the graph
+
+ for (var key in vertices)
+ {
+ var vertex = vertices[key];
+ this.setVertexLocation(vertex);
+
+ if (this.layout.resizeParent)
+ {
+ var parent = graph.model.getParent(vertex.cell);
+ var id = mxCellPath.create(parent);
+
+ // Implements set semantic
+ if (parentsChanged[id] == null)
+ {
+ parentsChanged[id] = parent;
+ }
+ }
+ }
+
+ if (this.layout.resizeParent && parentsChanged != null)
+ {
+ this.adjustParents(parentsChanged);
+ }
+
+ // Post process edge styles. Needs the vertex locations set for initial
+ // values of the top and bottoms of each rank
+ if (this.edgeStyle == mxHierarchicalEdgeStyle.ORTHOGONAL
+ || this.edgeStyle == mxHierarchicalEdgeStyle.POLYLINE)
+ {
+ this.localEdgeProcessing(model);
+ }
+
+ for (var key in edges)
+ {
+ this.setEdgePosition(edges[key]);
+ }
+};
+
+/**
+ * Function: adjustParents
+ *
+ * Adjust parent cells whose child geometries have changed. The default
+ * implementation adjusts the group to just fit around the children with
+ * a padding.
+ */
+mxCoordinateAssignment.prototype.adjustParents = function(parentsChanged)
+{
+ var tmp = [];
+
+ for (var id in parentsChanged)
+ {
+ tmp.push(parentsChanged[id]);
+ }
+
+ this.layout.arrangeGroups(mxUtils.sortCells(tmp, true), this.groupPadding);
+};
+
+/**
+ * Function: localEdgeProcessing
+ *
+ * Separates the x position of edges as they connect to vertices
+ *
+ * Parameters:
+ *
+ * model - the layout model
+ */
+mxCoordinateAssignment.prototype.localEdgeProcessing = function(model)
+{
+ var edgeMapping = model.edgeMapper;
+
+ // Iterate through each vertex, look at the edges connected in
+ // both directions.
+ for (var rankIndex = 0; rankIndex < model.ranks.length; rankIndex++)
+ {
+ var rank = model.ranks[rankIndex];
+
+ for (var cellIndex = 0; cellIndex < rank.length; cellIndex++)
+ {
+ var cell = rank[cellIndex];
+
+ if (cell.isVertex())
+ {
+ var currentCells = cell.getPreviousLayerConnectedCells(rankIndex);
+
+ var currentRank = rankIndex - 1;
+
+ // Two loops, last connected cells, and next
+ for (var k = 0; k < 2; k++)
+ {
+ if (currentRank > -1
+ && currentRank < model.ranks.length
+ && currentCells != null
+ && currentCells.length > 0)
+ {
+ var sortedCells = [];
+
+ for (var j = 0; j < currentCells.length; j++)
+ {
+ var sorter = new WeightedCellSorter(
+ currentCells[j], currentCells[j].getX(currentRank));
+ sortedCells.push(sorter);
+ }
+
+ sortedCells.sort(WeightedCellSorter.prototype.compare);
+
+ var leftLimit = cell.x[0] - cell.width / 2;
+ var rightLimit = leftLimit + cell.width;
+
+ // Connected edge count starts at 1 to allow for buffer
+ // with edge of vertex
+ var connectedEdgeCount = 0;
+ var connectedEdgeGroupCount = 0;
+ var connectedEdges = [];
+ // Calculate width requirements for all connected edges
+ for (var j = 0; j < sortedCells.length; j++)
+ {
+ var innerCell = sortedCells[j].cell;
+ var connections;
+
+ if (innerCell.isVertex())
+ {
+ // Get the connecting edge
+ if (k == 0)
+ {
+ connections = cell.connectsAsSource;
+
+ }
+ else
+ {
+ connections = cell.connectsAsTarget;
+ }
+
+ for (var connIndex = 0; connIndex < connections.length; connIndex++)
+ {
+ if (connections[connIndex].source == innerCell
+ || connections[connIndex].target == innerCell)
+ {
+ connectedEdgeCount += connections[connIndex].edges
+ .length;
+ connectedEdgeGroupCount++;
+
+ connectedEdges.push(connections[connIndex]);
+ }
+ }
+ }
+ else
+ {
+ connectedEdgeCount += innerCell.edges.length;
+ connectedEdgeGroupCount++;
+ connectedEdges.push(innerCell);
+ }
+ }
+
+ var requiredWidth = (connectedEdgeCount + 1)
+ * this.prefHozEdgeSep;
+
+ // Add a buffer on the edges of the vertex if the edge count allows
+ if (cell.width > requiredWidth
+ + (2 * this.prefHozEdgeSep))
+ {
+ leftLimit += this.prefHozEdgeSep;
+ rightLimit -= this.prefHozEdgeSep;
+ }
+
+ var availableWidth = rightLimit - leftLimit;
+ var edgeSpacing = availableWidth / connectedEdgeCount;
+
+ var currentX = leftLimit + edgeSpacing / 2.0;
+ var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
+ var maxYOffset = 0;
+
+ for (var j = 0; j < connectedEdges.length; j++)
+ {
+ var numActualEdges = connectedEdges[j].edges
+ .length;
+ var edgeId = mxCellPath.create(connectedEdges[j].edges[0]);
+ var pos = this.jettyPositions[edgeId];
+
+ if (pos == null)
+ {
+ pos = [];
+ this.jettyPositions[edgeId] = pos;
+ }
+
+ if (j < connectedEdgeCount / 2)
+ {
+ currentYOffset += this.prefVertEdgeOff;
+ }
+ else if (j > connectedEdgeCount / 2)
+ {
+ currentYOffset -= this.prefVertEdgeOff;
+ }
+ // Ignore the case if equals, this means the second of 2
+ // jettys with the same y (even number of edges)
+
+ for (var m = 0; m < numActualEdges; m++)
+ {
+ pos[m * 4 + k * 2] = currentX;
+ currentX += edgeSpacing;
+ pos[m * 4 + k * 2 + 1] = currentYOffset;
+ }
+
+ maxYOffset = Math.max(maxYOffset,
+ currentYOffset);
+ }
+ }
+
+ currentCells = cell.getNextLayerConnectedCells(rankIndex);
+
+ currentRank = rankIndex + 1;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: setEdgePosition
+ *
+ * Fixes the control points
+ */
+mxCoordinateAssignment.prototype.setEdgePosition = function(cell)
+{
+ // For parallel edges we need to seperate out the points a
+ // little
+ var offsetX = 0;
+ // Only set the edge control points once
+
+ if (cell.temp[0] != 101207)
+ {
+ var maxRank = cell.maxRank;
+ var minRank = cell.minRank;
+
+ if (maxRank == minRank)
+ {
+ maxRank = cell.source.maxRank;
+ minRank = cell.target.minRank;
+ }
+
+ var parallelEdgeCount = 0;
+ var edgeId = mxCellPath.create(cell.edges[0]);
+ var jettys = this.jettyPositions[edgeId];
+
+ var source = cell.isReversed ? cell.target.cell : cell.source.cell;
+
+ for (var i = 0; i < cell.edges.length; i++)
+ {
+ var realEdge = cell.edges[i];
+ var realSource = this.layout.graph.view.getVisibleTerminal(realEdge, true);
+
+ //List oldPoints = graph.getPoints(realEdge);
+ var newPoints = [];
+
+ // Single length reversed edges end up with the jettys in the wrong
+ // places. Since single length edges only have jettys, not segment
+ // control points, we just say the edge isn't reversed in this section
+ var reversed = cell.isReversed;
+
+ if (realSource != source)
+ {
+ // The real edges include all core model edges and these can go
+ // in both directions. If the source of the hierarchical model edge
+ // isn't the source of the specific real edge in this iteration
+ // treat if as reversed
+ reversed = !reversed;
+ }
+
+ // First jetty of edge
+ if (jettys != null)
+ {
+ var arrayOffset = reversed ? 2 : 0;
+ var y = reversed ? this.rankTopY[minRank] : this.rankBottomY[maxRank];
+ var jetty = jettys[parallelEdgeCount * 4 + 1 + arrayOffset];
+
+ if (reversed)
+ {
+ jetty = -jetty;
+ }
+
+ y += jetty;
+ var x = jettys[parallelEdgeCount * 4 + arrayOffset];
+
+ if (this.orientation == mxConstants.DIRECTION_NORTH
+ || this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ newPoints.push(new mxPoint(x, y));
+ }
+ else
+ {
+ newPoints.push(new mxPoint(y, x));
+ }
+ }
+
+ // Declare variables to define loop through edge points and
+ // change direction if edge is reversed
+
+ var loopStart = cell.x.length - 1;
+ var loopLimit = -1;
+ var loopDelta = -1;
+ var currentRank = cell.maxRank - 1;
+
+ if (reversed)
+ {
+ loopStart = 0;
+ loopLimit = cell.x.length;
+ loopDelta = 1;
+ currentRank = cell.minRank + 1;
+ }
+ // Reversed edges need the points inserted in
+ // reverse order
+ for (var j = loopStart; (cell.maxRank != cell.minRank) && j != loopLimit; j += loopDelta)
+ {
+ // The horizontal position in a vertical layout
+ var positionX = cell.x[j] + offsetX;
+
+ // Work out the vertical positions in a vertical layout
+ // in the edge buffer channels above and below this rank
+ var topChannelY = (this.rankTopY[currentRank] + this.rankBottomY[currentRank + 1]) / 2.0;
+ var bottomChannelY = (this.rankTopY[currentRank - 1] + this.rankBottomY[currentRank]) / 2.0;
+
+ if (reversed)
+ {
+ var tmp = topChannelY;
+ topChannelY = bottomChannelY;
+ bottomChannelY = tmp;
+ }
+
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ newPoints.push(new mxPoint(positionX, topChannelY));
+ newPoints.push(new mxPoint(positionX, bottomChannelY));
+ }
+ else
+ {
+ newPoints.push(new mxPoint(topChannelY, positionX));
+ newPoints.push(new mxPoint(bottomChannelY, positionX));
+ }
+
+ this.limitX = Math.max(this.limitX, positionX);
+ currentRank += loopDelta;
+ }
+
+ // Second jetty of edge
+ if (jettys != null)
+ {
+ var arrayOffset = reversed ? 2 : 0;
+ var rankY = reversed ? this.rankBottomY[maxRank] : this.rankTopY[minRank];
+ var jetty = jettys[parallelEdgeCount * 4 + 3 - arrayOffset];
+
+ if (reversed)
+ {
+ jetty = -jetty;
+ }
+ var y = rankY - jetty;
+ var x = jettys[parallelEdgeCount * 4 + 2 - arrayOffset];
+
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ newPoints.push(new mxPoint(x, y));
+ }
+ else
+ {
+ newPoints.push(new mxPoint(y, x));
+ }
+ }
+
+ if (cell.isReversed)
+ {
+ this.processReversedEdge(cell, realEdge);
+ }
+
+ this.layout.setEdgePoints(realEdge, newPoints);
+
+ // Increase offset so next edge is drawn next to
+ // this one
+ if (offsetX == 0.0)
+ {
+ offsetX = this.parallelEdgeSpacing;
+ }
+ else if (offsetX > 0)
+ {
+ offsetX = -offsetX;
+ }
+ else
+ {
+ offsetX = -offsetX + this.parallelEdgeSpacing;
+ }
+
+ parallelEdgeCount++;
+ }
+
+ cell.temp[0] = 101207;
+ }
+};
+
+
+/**
+ * Function: setVertexLocation
+ *
+ * Fixes the position of the specified vertex.
+ *
+ * Parameters:
+ *
+ * cell - the vertex to position
+ */
+mxCoordinateAssignment.prototype.setVertexLocation = function(cell)
+{
+ var realCell = cell.cell;
+ var positionX = cell.x[0] - cell.width / 2;
+ var positionY = cell.y[0] - cell.height / 2;
+
+ this.rankTopY[cell.minRank] = Math.min(this.rankTopY[cell.minRank], positionY);
+ this.rankBottomY[cell.minRank] = Math.max(this.rankBottomY[cell.minRank],
+ positionY + cell.height);
+
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ this.layout.setVertexLocation(realCell, positionX, positionY);
+ }
+ else
+ {
+ this.layout.setVertexLocation(realCell, positionY, positionX);
+ }
+
+ this.limitX = Math.max(this.limitX, positionX + cell.width);
+};
+
+/**
+ * Function: processReversedEdge
+ *
+ * Hook to add additional processing
+ *
+ * Parameters:
+ *
+ * edge - the hierarchical model edge
+ * realEdge - the real edge in the graph
+ */
+mxCoordinateAssignment.prototype.processReversedEdge = function(graph, model)
+{
+ // hook for subclassers
+};
+
+/**
+ * Class: WeightedCellSorter
+ *
+ * A utility class used to track cells whilst sorting occurs on the weighted
+ * sum of their connected edges. Does not violate (x.compareTo(y)==0) ==
+ * (x.equals(y))
+ *
+ * Constructor: WeightedCellSorter
+ *
+ * Constructs a new weighted cell sorted for the given cell and weight.
+ */
+function WeightedCellSorter(cell, weightedValue)
+{
+ this.cell = cell;
+ this.weightedValue = weightedValue;
+};
+
+/**
+ * Variable: weightedValue
+ *
+ * The weighted value of the cell stored.
+ */
+WeightedCellSorter.prototype.weightedValue = 0;
+
+/**
+ * Variable: nudge
+ *
+ * Whether or not to flip equal weight values.
+ */
+WeightedCellSorter.prototype.nudge = false;
+
+/**
+ * Variable: visited
+ *
+ * Whether or not this cell has been visited in the current assignment.
+ */
+WeightedCellSorter.prototype.visited = false;
+
+/**
+ * Variable: rankIndex
+ *
+ * The index this cell is in the model rank.
+ */
+WeightedCellSorter.prototype.rankIndex = null;
+
+/**
+ * Variable: cell
+ *
+ * The cell whose median value is being calculated.
+ */
+WeightedCellSorter.prototype.cell = null;
+
+/**
+ * Function: compare
+ *
+ * Compares two WeightedCellSorters.
+ */
+WeightedCellSorter.prototype.compare = function(a, b)
+{
+ if (a != null && b != null)
+ {
+ if (b.weightedValue > a.weightedValue)
+ {
+ return -1;
+ }
+ else if (b.weightedValue < a.weightedValue)
+ {
+ return 1;
+ }
+ else
+ {
+ if (b.nudge)
+ {
+ return -1;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ return 0;
+ }
+};
diff --git a/src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js b/src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
new file mode 100644
index 0000000..2e635fc
--- /dev/null
+++ b/src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
@@ -0,0 +1,25 @@
+/**
+ * $Id: mxHierarchicalLayoutStage.js,v 1.8 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxHierarchicalLayoutStage
+ *
+ * The specific layout interface for hierarchical layouts. It adds a
+ * <code>run</code> method with a parameter for the hierarchical layout model
+ * that is shared between the layout stages.
+ *
+ * Constructor: mxHierarchicalLayoutStage
+ *
+ * Constructs a new hierarchical layout stage.
+ */
+function mxHierarchicalLayoutStage() { };
+
+/**
+ * Function: execute
+ *
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxHierarchicalLayoutStage.prototype.execute = function(parent) { };
diff --git a/src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js b/src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
new file mode 100644
index 0000000..997890e
--- /dev/null
+++ b/src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
@@ -0,0 +1,674 @@
+/**
+ * $Id: mxMedianHybridCrossingReduction.js,v 1.25 2012-06-07 11:16:41 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxMedianHybridCrossingReduction
+ *
+ * Sets the horizontal locations of node and edge dummy nodes on each layer.
+ * Uses median down and up weighings as well heuristic to straighten edges as
+ * far as possible.
+ *
+ * Constructor: mxMedianHybridCrossingReduction
+ *
+ * Creates a coordinate assignment.
+ *
+ * Arguments:
+ *
+ * intraCellSpacing - the minimum buffer between cells on the same rank
+ * interRankCellSpacing - the minimum distance between cells on adjacent ranks
+ * orientation - the position of the root node(s) relative to the graph
+ * initialX - the leftmost coordinate node placement starts at
+ */
+function mxMedianHybridCrossingReduction(layout)
+{
+ this.layout = layout;
+};
+
+/**
+ * Extends mxMedianHybridCrossingReduction.
+ */
+mxMedianHybridCrossingReduction.prototype = new mxHierarchicalLayoutStage();
+mxMedianHybridCrossingReduction.prototype.constructor = mxMedianHybridCrossingReduction;
+
+/**
+ * Variable: layout
+ *
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxMedianHybridCrossingReduction.prototype.layout = null;
+
+/**
+ * Variable: maxIterations
+ *
+ * The maximum number of iterations to perform whilst reducing edge
+ * crossings. Default is 24.
+ */
+mxMedianHybridCrossingReduction.prototype.maxIterations = 24;
+
+/**
+ * Variable: nestedBestRanks
+ *
+ * Stores each rank as a collection of cells in the best order found for
+ * each layer so far
+ */
+mxMedianHybridCrossingReduction.prototype.nestedBestRanks = null;
+
+/**
+ * Variable: currentBestCrossings
+ *
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.currentBestCrossings = 0;
+
+/**
+ * Variable: iterationsWithoutImprovement
+ *
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement = 0;
+
+/**
+ * Variable: maxNoImprovementIterations
+ *
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations = 2;
+
+/**
+ * Function: execute
+ *
+ * Performs a vertex ordering within ranks as described by Gansner et al
+ * 1993
+ */
+mxMedianHybridCrossingReduction.prototype.execute = function(parent)
+{
+ var model = this.layout.getModel();
+
+ // Stores initial ordering as being the best one found so far
+ this.nestedBestRanks = [];
+
+ for (var i = 0; i < model.ranks.length; i++)
+ {
+ this.nestedBestRanks[i] = model.ranks[i].slice();
+ }
+
+ var iterationsWithoutImprovement = 0;
+ var currentBestCrossings = this.calculateCrossings(model);
+
+ for (var i = 0; i < this.maxIterations &&
+ iterationsWithoutImprovement < this.maxNoImprovementIterations; i++)
+ {
+ this.weightedMedian(i, model);
+ this.transpose(i, model);
+ var candidateCrossings = this.calculateCrossings(model);
+
+ if (candidateCrossings < currentBestCrossings)
+ {
+ currentBestCrossings = candidateCrossings;
+ iterationsWithoutImprovement = 0;
+
+ // Store the current rankings as the best ones
+ for (var j = 0; j < this.nestedBestRanks.length; j++)
+ {
+ var rank = model.ranks[j];
+
+ for (var k = 0; k < rank.length; k++)
+ {
+ var cell = rank[k];
+ this.nestedBestRanks[j][cell.getGeneralPurposeVariable(j)] = cell;
+ }
+ }
+ }
+ else
+ {
+ // Increase count of iterations where we haven't improved the
+ // layout
+ iterationsWithoutImprovement++;
+
+ // Restore the best values to the cells
+ for (var j = 0; j < this.nestedBestRanks.length; j++)
+ {
+ var rank = model.ranks[j];
+
+ for (var k = 0; k < rank.length; k++)
+ {
+ var cell = rank[k];
+ cell.setGeneralPurposeVariable(j, k);
+ }
+ }
+ }
+
+ if (currentBestCrossings == 0)
+ {
+ // Do nothing further
+ break;
+ }
+ }
+
+ // Store the best rankings but in the model
+ var ranks = [];
+ var rankList = [];
+
+ for (var i = 0; i < model.maxRank + 1; i++)
+ {
+ rankList[i] = [];
+ ranks[i] = rankList[i];
+ }
+
+ for (var i = 0; i < this.nestedBestRanks.length; i++)
+ {
+ for (var j = 0; j < this.nestedBestRanks[i].length; j++)
+ {
+ rankList[i].push(this.nestedBestRanks[i][j]);
+ }
+ }
+
+ model.ranks = ranks;
+};
+
+
+/**
+ * Function: calculateCrossings
+ *
+ * Calculates the total number of edge crossing in the current graph.
+ * Returns the current number of edge crossings in the hierarchy graph
+ * model in the current candidate layout
+ *
+ * Parameters:
+ *
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.calculateCrossings = function(model)
+{
+ var numRanks = model.ranks.length;
+ var totalCrossings = 0;
+
+ for (var i = 1; i < numRanks; i++)
+ {
+ totalCrossings += this.calculateRankCrossing(i, model);
+ }
+
+ return totalCrossings;
+};
+
+/**
+ * Function: calculateRankCrossing
+ *
+ * Calculates the number of edges crossings between the specified rank and
+ * the rank below it. Returns the number of edges crossings with the rank
+ * beneath
+ *
+ * Parameters:
+ *
+ * i - the topmost rank of the pair ( higher rank value )
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.calculateRankCrossing = function(i, model)
+{
+ var totalCrossings = 0;
+ var rank = model.ranks[i];
+ var previousRank = model.ranks[i - 1];
+
+ // Create an array of connections between these two levels
+ var currentRankSize = rank.length;
+ var previousRankSize = previousRank.length;
+ var connections = [];
+
+ for (var j = 0; j < currentRankSize; j++)
+ {
+ connections[j] = [];
+ }
+
+ // Iterate over the top rank and fill in the connection information
+ for (var j = 0; j < rank.length; j++)
+ {
+ var node = rank[j];
+ var rankPosition = node.getGeneralPurposeVariable(i);
+ var connectedCells = node.getPreviousLayerConnectedCells(i);
+
+ for (var k = 0; k < connectedCells.length; k++)
+ {
+ var connectedNode = connectedCells[k];
+ var otherCellRankPosition = connectedNode.getGeneralPurposeVariable(i - 1);
+ connections[rankPosition][otherCellRankPosition] = 201207;
+ }
+ }
+
+ // Iterate through the connection matrix, crossing edges are
+ // indicated by other connected edges with a greater rank position
+ // on one rank and lower position on the other
+ for (var j = 0; j < currentRankSize; j++)
+ {
+ for (var k = 0; k < previousRankSize; k++)
+ {
+ if (connections[j][k] == 201207)
+ {
+ // Draw a grid of connections, crossings are top right
+ // and lower left from this crossing pair
+ for (var j2 = j + 1; j2 < currentRankSize; j2++)
+ {
+ for (var k2 = 0; k2 < k; k2++)
+ {
+ if (connections[j2][k2] == 201207)
+ {
+ totalCrossings++;
+ }
+ }
+ }
+
+ for (var j2 = 0; j2 < j; j2++)
+ {
+ for (var k2 = k + 1; k2 < previousRankSize; k2++)
+ {
+ if (connections[j2][k2] == 201207)
+ {
+ totalCrossings++;
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ return totalCrossings / 2;
+};
+
+/**
+ * Function: transpose
+ *
+ * Takes each possible adjacent cell pair on each rank and checks if
+ * swapping them around reduces the number of crossing
+ *
+ * Parameters:
+ *
+ * mainLoopIteration - the iteration number of the main loop
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.transpose = function(mainLoopIteration, model)
+{
+ var improved = true;
+
+ // Track the number of iterations in case of looping
+ var count = 0;
+ var maxCount = 10;
+ while (improved && count++ < maxCount)
+ {
+ // On certain iterations allow allow swapping of cell pairs with
+ // equal edge crossings switched or not switched. This help to
+ // nudge a stuck layout into a lower crossing total.
+ var nudge = mainLoopIteration % 2 == 1 && count % 2 == 1;
+ improved = false;
+
+ for (var i = 0; i < model.ranks.length; i++)
+ {
+ var rank = model.ranks[i];
+ var orderedCells = [];
+
+ for (var j = 0; j < rank.length; j++)
+ {
+ var cell = rank[j];
+ var tempRank = cell.getGeneralPurposeVariable(i);
+
+ // FIXME: Workaround to avoid negative tempRanks
+ if (tempRank < 0)
+ {
+ tempRank = j;
+ }
+ orderedCells[tempRank] = cell;
+ }
+
+ var leftCellAboveConnections = null;
+ var leftCellBelowConnections = null;
+ var rightCellAboveConnections = null;
+ var rightCellBelowConnections = null;
+
+ var leftAbovePositions = null;
+ var leftBelowPositions = null;
+ var rightAbovePositions = null;
+ var rightBelowPositions = null;
+
+ var leftCell = null;
+ var rightCell = null;
+
+ for (var j = 0; j < (rank.length - 1); j++)
+ {
+ // For each intra-rank adjacent pair of cells
+ // see if swapping them around would reduce the
+ // number of edges crossing they cause in total
+ // On every cell pair except the first on each rank, we
+ // can save processing using the previous values for the
+ // right cell on the new left cell
+ if (j == 0)
+ {
+ leftCell = orderedCells[j];
+ leftCellAboveConnections = leftCell
+ .getNextLayerConnectedCells(i);
+ leftCellBelowConnections = leftCell
+ .getPreviousLayerConnectedCells(i);
+ leftAbovePositions = [];
+ leftBelowPositions = [];
+
+ for (var k = 0; k < leftCellAboveConnections.length; k++)
+ {
+ leftAbovePositions[k] = leftCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
+ }
+
+ for (var k = 0; k < leftCellBelowConnections.length; k++)
+ {
+ leftBelowPositions[k] = leftCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
+ }
+ }
+ else
+ {
+ leftCellAboveConnections = rightCellAboveConnections;
+ leftCellBelowConnections = rightCellBelowConnections;
+ leftAbovePositions = rightAbovePositions;
+ leftBelowPositions = rightBelowPositions;
+ leftCell = rightCell;
+ }
+
+ rightCell = orderedCells[j + 1];
+ rightCellAboveConnections = rightCell
+ .getNextLayerConnectedCells(i);
+ rightCellBelowConnections = rightCell
+ .getPreviousLayerConnectedCells(i);
+
+ rightAbovePositions = [];
+ rightBelowPositions = [];
+
+ for (var k = 0; k < rightCellAboveConnections.length; k++)
+ {
+ rightAbovePositions[k] = rightCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
+ }
+
+ for (var k = 0; k < rightCellBelowConnections.length; k++)
+ {
+ rightBelowPositions[k] = rightCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
+ }
+
+ var totalCurrentCrossings = 0;
+ var totalSwitchedCrossings = 0;
+
+ for (var k = 0; k < leftAbovePositions.length; k++)
+ {
+ for (var ik = 0; ik < rightAbovePositions.length; ik++)
+ {
+ if (leftAbovePositions[k] > rightAbovePositions[ik])
+ {
+ totalCurrentCrossings++;
+ }
+
+ if (leftAbovePositions[k] < rightAbovePositions[ik])
+ {
+ totalSwitchedCrossings++;
+ }
+ }
+ }
+
+ for (var k = 0; k < leftBelowPositions.length; k++)
+ {
+ for (var ik = 0; ik < rightBelowPositions.length; ik++)
+ {
+ if (leftBelowPositions[k] > rightBelowPositions[ik])
+ {
+ totalCurrentCrossings++;
+ }
+
+ if (leftBelowPositions[k] < rightBelowPositions[ik])
+ {
+ totalSwitchedCrossings++;
+ }
+ }
+ }
+
+ if ((totalSwitchedCrossings < totalCurrentCrossings) ||
+ (totalSwitchedCrossings == totalCurrentCrossings &&
+ nudge))
+ {
+ var temp = leftCell.getGeneralPurposeVariable(i);
+ leftCell.setGeneralPurposeVariable(i, rightCell
+ .getGeneralPurposeVariable(i));
+ rightCell.setGeneralPurposeVariable(i, temp);
+
+ // With this pair exchanged we have to switch all of
+ // values for the left cell to the right cell so the
+ // next iteration for this rank uses it as the left
+ // cell again
+ rightCellAboveConnections = leftCellAboveConnections;
+ rightCellBelowConnections = leftCellBelowConnections;
+ rightAbovePositions = leftAbovePositions;
+ rightBelowPositions = leftBelowPositions;
+ rightCell = leftCell;
+
+ if (!nudge)
+ {
+ // Don't count nudges as improvement or we'll end
+ // up stuck in two combinations and not finishing
+ // as early as we should
+ improved = true;
+ }
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: weightedMedian
+ *
+ * Sweeps up or down the layout attempting to minimise the median placement
+ * of connected cells on adjacent ranks
+ *
+ * Parameters:
+ *
+ * iteration - the iteration number of the main loop
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.weightedMedian = function(iteration, model)
+{
+ // Reverse sweep direction each time through this method
+ var downwardSweep = (iteration % 2 == 0);
+ if (downwardSweep)
+ {
+ for (var j = model.maxRank - 1; j >= 0; j--)
+ {
+ this.medianRank(j, downwardSweep);
+ }
+ }
+ else
+ {
+ for (var j = 1; j < model.maxRank; j++)
+ {
+ this.medianRank(j, downwardSweep);
+ }
+ }
+};
+
+/**
+ * Function: medianRank
+ *
+ * Attempts to minimise the median placement of connected cells on this rank
+ * and one of the adjacent ranks
+ *
+ * Parameters:
+ *
+ * rankValue - the layer number of this rank
+ * downwardSweep - whether or not this is a downward sweep through the graph
+ */
+mxMedianHybridCrossingReduction.prototype.medianRank = function(rankValue, downwardSweep)
+{
+ var numCellsForRank = this.nestedBestRanks[rankValue].length;
+ var medianValues = [];
+ var reservedPositions = [];
+
+ for (var i = 0; i < numCellsForRank; i++)
+ {
+ var cell = this.nestedBestRanks[rankValue][i];
+ var sorterEntry = new MedianCellSorter();
+ sorterEntry.cell = cell;
+
+ // Flip whether or not equal medians are flipped on up and down
+ // sweeps
+ // TODO re-implement some kind of nudge
+ // medianValues[i].nudge = !downwardSweep;
+ var nextLevelConnectedCells;
+
+ if (downwardSweep)
+ {
+ nextLevelConnectedCells = cell
+ .getNextLayerConnectedCells(rankValue);
+ }
+ else
+ {
+ nextLevelConnectedCells = cell
+ .getPreviousLayerConnectedCells(rankValue);
+ }
+
+ var nextRankValue;
+
+ if (downwardSweep)
+ {
+ nextRankValue = rankValue + 1;
+ }
+ else
+ {
+ nextRankValue = rankValue - 1;
+ }
+
+ if (nextLevelConnectedCells != null
+ && nextLevelConnectedCells.length != 0)
+ {
+ sorterEntry.medianValue = this.medianValue(
+ nextLevelConnectedCells, nextRankValue);
+ medianValues.push(sorterEntry);
+ }
+ else
+ {
+ // Nodes with no adjacent vertices are flagged in the reserved array
+ // to indicate they should be left in their current position.
+ reservedPositions[cell.getGeneralPurposeVariable(rankValue)] = true;
+ }
+ }
+
+ medianValues.sort(MedianCellSorter.prototype.compare);
+
+ // Set the new position of each node within the rank using
+ // its temp variable
+ for (var i = 0; i < numCellsForRank; i++)
+ {
+ if (reservedPositions[i] == null)
+ {
+ var cell = medianValues.shift().cell;
+ cell.setGeneralPurposeVariable(rankValue, i);
+ }
+ }
+};
+
+/**
+ * Function: medianValue
+ *
+ * Calculates the median rank order positioning for the specified cell using
+ * the connected cells on the specified rank. Returns the median rank
+ * ordering value of the connected cells
+ *
+ * Parameters:
+ *
+ * connectedCells - the cells on the specified rank connected to the
+ * specified cell
+ * rankValue - the rank that the connected cell lie upon
+ */
+mxMedianHybridCrossingReduction.prototype.medianValue = function(connectedCells, rankValue)
+{
+ var medianValues = [];
+ var arrayCount = 0;
+
+ for (var i = 0; i < connectedCells.length; i++)
+ {
+ var cell = connectedCells[i];
+ medianValues[arrayCount++] = cell.getGeneralPurposeVariable(rankValue);
+ }
+
+ // Sort() sorts lexicographically by default (i.e. 11 before 9) so force
+ // numerical order sort
+ medianValues.sort(function(a,b){return a - b;});
+
+ if (arrayCount % 2 == 1)
+ {
+ // For odd numbers of adjacent vertices return the median
+ return medianValues[Math.floor(arrayCount / 2)];
+ }
+ else if (arrayCount == 2)
+ {
+ return ((medianValues[0] + medianValues[1]) / 2.0);
+ }
+ else
+ {
+ var medianPoint = arrayCount / 2;
+ var leftMedian = medianValues[medianPoint - 1] - medianValues[0];
+ var rightMedian = medianValues[arrayCount - 1]
+ - medianValues[medianPoint];
+
+ return (medianValues[medianPoint - 1] * rightMedian + medianValues[medianPoint]
+ * leftMedian)
+ / (leftMedian + rightMedian);
+ }
+};
+
+/**
+ * Class: MedianCellSorter
+ *
+ * A utility class used to track cells whilst sorting occurs on the median
+ * values. Does not violate (x.compareTo(y)==0) == (x.equals(y))
+ *
+ * Constructor: MedianCellSorter
+ *
+ * Constructs a new median cell sorter.
+ */
+function MedianCellSorter()
+{
+ // empty
+};
+
+/**
+ * Variable: medianValue
+ *
+ * The weighted value of the cell stored.
+ */
+MedianCellSorter.prototype.medianValue = 0;
+
+/**
+ * Variable: cell
+ *
+ * The cell whose median value is being calculated
+ */
+MedianCellSorter.prototype.cell = false;
+
+/**
+ * Function: compare
+ *
+ * Compares two MedianCellSorters.
+ */
+MedianCellSorter.prototype.compare = function(a, b)
+{
+ if (a != null && b != null)
+ {
+ if (b.medianValue > a.medianValue)
+ {
+ return -1;
+ }
+ else if (b.medianValue < a.medianValue)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ return 0;
+ }
+};
diff --git a/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js b/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
new file mode 100644
index 0000000..4f18f62
--- /dev/null
+++ b/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
@@ -0,0 +1,131 @@
+/**
+ * $Id: mxMinimumCycleRemover.js,v 1.14 2010-01-04 11:18:26 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxMinimumCycleRemover
+ *
+ * An implementation of the first stage of the Sugiyama layout. Straightforward
+ * longest path calculation of layer assignment
+ *
+ * Constructor: mxMinimumCycleRemover
+ *
+ * Creates a cycle remover for the given internal model.
+ */
+function mxMinimumCycleRemover(layout)
+{
+ this.layout = layout;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxMinimumCycleRemover.prototype = new mxHierarchicalLayoutStage();
+mxMinimumCycleRemover.prototype.constructor = mxMinimumCycleRemover;
+
+/**
+ * Variable: layout
+ *
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxMinimumCycleRemover.prototype.layout = null;
+
+/**
+ * Function: execute
+ *
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxMinimumCycleRemover.prototype.execute = function(parent)
+{
+ var model = this.layout.getModel();
+ var seenNodes = new Object();
+ var unseenNodes = mxUtils.clone(model.vertexMapper, null, true);
+
+ // Perform a dfs through the internal model. If a cycle is found,
+ // reverse it.
+ var rootsArray = null;
+
+ if (model.roots != null)
+ {
+ var modelRoots = model.roots;
+ rootsArray = [];
+
+ for (var i = 0; i < modelRoots.length; i++)
+ {
+ var nodeId = mxCellPath.create(modelRoots[i]);
+ rootsArray[i] = model.vertexMapper[nodeId];
+ }
+ }
+
+ model.visit(function(parent, node, connectingEdge, layer, seen)
+ {
+ // Check if the cell is in it's own ancestor list, if so
+ // invert the connecting edge and reverse the target/source
+ // relationship to that edge in the parent and the cell
+ if (node.isAncestor(parent))
+ {
+ connectingEdge.invert();
+ mxUtils.remove(connectingEdge, parent.connectsAsSource);
+ parent.connectsAsTarget.push(connectingEdge);
+ mxUtils.remove(connectingEdge, node.connectsAsTarget);
+ node.connectsAsSource.push(connectingEdge);
+ }
+
+ var cellId = mxCellPath.create(node.cell);
+ seenNodes[cellId] = node;
+ delete unseenNodes[cellId];
+ }, rootsArray, true, null);
+
+ var possibleNewRoots = null;
+
+ if (unseenNodes.lenth > 0)
+ {
+ possibleNewRoots = mxUtils.clone(unseenNodes, null, true);
+ }
+
+ // If there are any nodes that should be nodes that the dfs can miss
+ // these need to be processed with the dfs and the roots assigned
+ // correctly to form a correct internal model
+ var seenNodesCopy = mxUtils.clone(seenNodes, null, true);
+
+ // Pick a random cell and dfs from it
+ model.visit(function(parent, node, connectingEdge, layer, seen)
+ {
+ // Check if the cell is in it's own ancestor list, if so
+ // invert the connecting edge and reverse the target/source
+ // relationship to that edge in the parent and the cell
+ if (node.isAncestor(parent))
+ {
+ connectingEdge.invert();
+ mxUtils.remove(connectingEdge, parent.connectsAsSource);
+ node.connectsAsSource.push(connectingEdge);
+ parent.connectsAsTarget.push(connectingEdge);
+ mxUtils.remove(connectingEdge, node.connectsAsTarget);
+ }
+
+ var cellId = mxCellPath.create(node.cell);
+ seenNodes[cellId] = node;
+ delete unseenNodes[cellId];
+ }, unseenNodes, true, seenNodesCopy);
+
+ var graph = this.layout.getGraph();
+
+ if (possibleNewRoots != null && possibleNewRoots.length > 0)
+ {
+ var roots = model.roots;
+
+ for (var i = 0; i < possibleNewRoots.length; i++)
+ {
+ var node = possibleNewRoots[i];
+ var realNode = node.cell;
+ var numIncomingEdges = graph.getIncomingEdges(realNode).length;
+
+ if (numIncomingEdges == 0)
+ {
+ roots.push(realNode);
+ }
+ }
+ }
+};
diff --git a/src/js/layout/mxCircleLayout.js b/src/js/layout/mxCircleLayout.js
new file mode 100644
index 0000000..e3e6ec1
--- /dev/null
+++ b/src/js/layout/mxCircleLayout.js
@@ -0,0 +1,203 @@
+/**
+ * $Id: mxCircleLayout.js,v 1.25 2012-08-22 17:26:12 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCircleLayout
+ *
+ * Extends <mxGraphLayout> to implement a circluar layout for a given radius.
+ * The vertices do not need to be connected for this layout to work and all
+ * connections between vertices are not taken into account.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxCircleLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxCircleLayout
+ *
+ * Constructs a new circular layout for the specified radius.
+ *
+ * Arguments:
+ *
+ * graph - <mxGraph> that contains the cells.
+ * radius - Optional radius as an int. Default is 100.
+ */
+function mxCircleLayout(graph, radius)
+{
+ mxGraphLayout.call(this, graph);
+ this.radius = (radius != null) ? radius : 100;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCircleLayout.prototype = new mxGraphLayout();
+mxCircleLayout.prototype.constructor = mxCircleLayout;
+
+/**
+ * Variable: radius
+ *
+ * Integer specifying the size of the radius. Default is 100.
+ */
+mxCircleLayout.prototype.radius = null;
+
+/**
+ * Variable: moveCircle
+ *
+ * Boolean specifying if the circle should be moved to the top,
+ * left corner specified by <x0> and <y0>. Default is false.
+ */
+mxCircleLayout.prototype.moveCircle = false;
+
+/**
+ * Variable: x0
+ *
+ * Integer specifying the left coordinate of the circle.
+ * Default is 0.
+ */
+mxCircleLayout.prototype.x0 = 0;
+
+/**
+ * Variable: y0
+ *
+ * Integer specifying the top coordinate of the circle.
+ * Default is 0.
+ */
+mxCircleLayout.prototype.y0 = 0;
+
+/**
+ * Variable: resetEdges
+ *
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxCircleLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: disableEdgeStyle
+ *
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxCircleLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>.
+ */
+mxCircleLayout.prototype.execute = function(parent)
+{
+ var model = this.graph.getModel();
+
+ // Moves the vertices to build a circle. Makes sure the
+ // radius is large enough for the vertices to not
+ // overlap
+ model.beginUpdate();
+ try
+ {
+ // Gets all vertices inside the parent and finds
+ // the maximum dimension of the largest vertex
+ var max = 0;
+ var top = null;
+ var left = null;
+ var vertices = [];
+ var childCount = model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cell = model.getChildAt(parent, i);
+
+ if (!this.isVertexIgnored(cell))
+ {
+ vertices.push(cell);
+ var bounds = this.getVertexBounds(cell);
+
+ if (top == null)
+ {
+ top = bounds.y;
+ }
+ else
+ {
+ top = Math.min(top, bounds.y);
+ }
+
+ if (left == null)
+ {
+ left = bounds.x;
+ }
+ else
+ {
+ left = Math.min(left, bounds.x);
+ }
+
+ max = Math.max(max, Math.max(bounds.width, bounds.height));
+ }
+ else if (!this.isEdgeIgnored(cell))
+ {
+ // Resets the points on the traversed edge
+ if (this.resetEdges)
+ {
+ this.graph.resetEdge(cell);
+ }
+
+ if (this.disableEdgeStyle)
+ {
+ this.setEdgeStyleEnabled(cell, false);
+ }
+ }
+ }
+
+ var r = this.getRadius(vertices.length, max);
+
+ // Moves the circle to the specified origin
+ if (this.moveCircle)
+ {
+ left = this.x0;
+ top = this.y0;
+ }
+
+ this.circle(vertices, r, left, top);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: getRadius
+ *
+ * Returns the radius to be used for the given vertex count. Max is the maximum
+ * width or height of all vertices in the layout.
+ */
+mxCircleLayout.prototype.getRadius = function(count, max)
+{
+ return Math.max(count * max / Math.PI, this.radius);
+};
+
+/**
+ * Function: circle
+ *
+ * Executes the circular layout for the specified array
+ * of vertices and the given radius. This is called from
+ * <execute>.
+ */
+mxCircleLayout.prototype.circle = function(vertices, r, left, top)
+{
+ var vertexCount = vertices.length;
+ var phi = 2 * Math.PI / vertexCount;
+
+ for (var i = 0; i < vertexCount; i++)
+ {
+ if (this.isVertexMovable(vertices[i]))
+ {
+ this.setVertexLocation(vertices[i],
+ left + r + r * Math.sin(i*phi),
+ top + r + r * Math.cos(i*phi));
+ }
+ }
+};
diff --git a/src/js/layout/mxCompactTreeLayout.js b/src/js/layout/mxCompactTreeLayout.js
new file mode 100644
index 0000000..db6324c
--- /dev/null
+++ b/src/js/layout/mxCompactTreeLayout.js
@@ -0,0 +1,995 @@
+/**
+ * $Id: mxCompactTreeLayout.js,v 1.57 2012-05-24 13:09:34 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCompactTreeLayout
+ *
+ * Extends <mxGraphLayout> to implement a compact tree (Moen) algorithm. This
+ * layout is suitable for graphs that have no cycles (trees). Vertices that are
+ * not connected to the tree will be ignored by this layout.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxCompactTreeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxCompactTreeLayout
+ *
+ * Constructs a new compact tree layout for the specified graph
+ * and orientation.
+ */
+function mxCompactTreeLayout(graph, horizontal, invert)
+{
+ mxGraphLayout.call(this, graph);
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.invert = (invert != null) ? invert : false;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCompactTreeLayout.prototype = new mxGraphLayout();
+mxCompactTreeLayout.prototype.constructor = mxCompactTreeLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the layout. Default is true.
+ */
+mxCompactTreeLayout.prototype.horizontal = null;
+
+/**
+ * Variable: invert
+ *
+ * Specifies if edge directions should be inverted. Default is false.
+ */
+mxCompactTreeLayout.prototype.invert = null;
+
+/**
+ * Variable: resizeParent
+ *
+ * If the parents should be resized to match the width/height of the
+ * children. Default is true.
+ */
+mxCompactTreeLayout.prototype.resizeParent = true;
+
+/**
+ * Variable: groupPadding
+ *
+ * Padding added to resized parents
+ */
+mxCompactTreeLayout.prototype.groupPadding = 10;
+
+/**
+ * Variable: parentsChanged
+ *
+ * A set of the parents that need updating based on children
+ * process as part of the layout
+ */
+mxCompactTreeLayout.prototype.parentsChanged = null;
+
+/**
+ * Variable: moveTree
+ *
+ * Specifies if the tree should be moved to the top, left corner
+ * if it is inside a top-level layer. Default is false.
+ */
+mxCompactTreeLayout.prototype.moveTree = false;
+
+/**
+ * Variable: levelDistance
+ *
+ * Holds the levelDistance. Default is 10.
+ */
+mxCompactTreeLayout.prototype.levelDistance = 10;
+
+/**
+ * Variable: nodeDistance
+ *
+ * Holds the nodeDistance. Default is 20.
+ */
+mxCompactTreeLayout.prototype.nodeDistance = 20;
+
+/**
+ * Variable: resetEdges
+ *
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxCompactTreeLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: prefHozEdgeSep
+ *
+ * The preferred horizontal distance between edges exiting a vertex
+ */
+mxCompactTreeLayout.prototype.prefHozEdgeSep = 5;
+
+/**
+ * Variable: prefVertEdgeOff
+ *
+ * The preferred vertical offset between edges exiting a vertex
+ */
+mxCompactTreeLayout.prototype.prefVertEdgeOff = 4;
+
+/**
+ * Variable: minEdgeJetty
+ *
+ * The minimum distance for an edge jetty from a vertex
+ */
+mxCompactTreeLayout.prototype.minEdgeJetty = 8;
+
+/**
+ * Variable: channelBuffer
+ *
+ * The size of the vertical buffer in the center of inter-rank channels
+ * where edge control points should not be placed
+ */
+mxCompactTreeLayout.prototype.channelBuffer = 4;
+
+/**
+ * Variable: edgeRouting
+ *
+ * Whether or not to apply the internal tree edge routing
+ */
+mxCompactTreeLayout.prototype.edgeRouting = true;
+
+/**
+ * Function: isVertexIgnored
+ *
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxCompactTreeLayout.prototype.isVertexIgnored = function(vertex)
+{
+ return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+ this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: isHorizontal
+ *
+ * Returns <horizontal>.
+ */
+mxCompactTreeLayout.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>.
+ *
+ * If the parent has any connected edges, then it is used as the root of
+ * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
+ * root node within the set of children of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be laid out.
+ * root - Optional <mxCell> that will be used as the root of the tree.
+ */
+mxCompactTreeLayout.prototype.execute = function(parent, root)
+{
+ this.parent = parent;
+ var model = this.graph.getModel();
+
+ if (root == null)
+ {
+ // Takes the parent as the root if it has outgoing edges
+ if (this.graph.getEdges(parent, model.getParent(parent),
+ this.invert, !this.invert, false).length > 0)
+ {
+ root = parent;
+ }
+
+ // Tries to find a suitable root in the parent's
+ // children
+ else
+ {
+ var roots = this.graph.findTreeRoots(parent, true, this.invert);
+
+ if (roots.length > 0)
+ {
+ for (var i = 0; i < roots.length; i++)
+ {
+ if (!this.isVertexIgnored(roots[i]) &&
+ this.graph.getEdges(roots[i], null,
+ this.invert, !this.invert, false).length > 0)
+ {
+ root = roots[i];
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (root != null)
+ {
+ if (this.resizeParent)
+ {
+ this.parentsChanged = new Object();
+ }
+ else
+ {
+ this.parentsChanged = null;
+ }
+
+ model.beginUpdate();
+
+ try
+ {
+ var node = this.dfs(root, parent);
+
+ if (node != null)
+ {
+ this.layout(node);
+ var x0 = this.graph.gridSize;
+ var y0 = x0;
+
+ if (!this.moveTree)
+ {
+ var g = this.getVertexBounds(root);
+
+ if (g != null)
+ {
+ x0 = g.x;
+ y0 = g.y;
+ }
+ }
+
+ var bounds = null;
+
+ if (this.isHorizontal())
+ {
+ bounds = this.horizontalLayout(node, x0, y0);
+ }
+ else
+ {
+ bounds = this.verticalLayout(node, null, x0, y0);
+ }
+
+ if (bounds != null)
+ {
+ var dx = 0;
+ var dy = 0;
+
+ if (bounds.x < 0)
+ {
+ dx = Math.abs(x0 - bounds.x);
+ }
+
+ if (bounds.y < 0)
+ {
+ dy = Math.abs(y0 - bounds.y);
+ }
+
+ if (dx != 0 || dy != 0)
+ {
+ this.moveNode(node, dx, dy);
+ }
+
+ if (this.resizeParent)
+ {
+ this.adjustParents();
+ }
+
+ if (this.edgeRouting)
+ {
+ // Iterate through all edges setting their positions
+ this.localEdgeProcessing(node);
+ }
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: moveNode
+ *
+ * Moves the specified node and all of its children by the given amount.
+ */
+mxCompactTreeLayout.prototype.moveNode = function(node, dx, dy)
+{
+ node.x += dx;
+ node.y += dy;
+ this.apply(node);
+
+ var child = node.child;
+
+ while (child != null)
+ {
+ this.moveNode(child, dx, dy);
+ child = child.next;
+ }
+};
+
+/**
+ * Function: dfs
+ *
+ * Does a depth first search starting at the specified cell.
+ * Makes sure the specified parent is never left by the
+ * algorithm.
+ */
+mxCompactTreeLayout.prototype.dfs = function(cell, parent, visited)
+{
+ visited = (visited != null) ? visited : [];
+
+ var id = mxCellPath.create(cell);
+ var node = null;
+
+ if (cell != null && visited[id] == null && !this.isVertexIgnored(cell))
+ {
+ visited[id] = cell;
+ node = this.createNode(cell);
+
+ var model = this.graph.getModel();
+ var prev = null;
+ var out = this.graph.getEdges(cell, parent, this.invert, !this.invert, false, true);
+ var view = this.graph.getView();
+
+ for (var i = 0; i < out.length; i++)
+ {
+ var edge = out[i];
+
+ if (!this.isEdgeIgnored(edge))
+ {
+ // Resets the points on the traversed edge
+ if (this.resetEdges)
+ {
+ this.setEdgePoints(edge, null);
+ }
+
+ if (this.edgeRouting)
+ {
+ this.setEdgeStyleEnabled(edge, false);
+ this.setEdgePoints(edge, null);
+ }
+
+ // Checks if terminal in same swimlane
+ var state = view.getState(edge);
+ var target = (state != null) ? state.getVisibleTerminal(this.invert) : view.getVisibleTerminal(edge, this.invert);
+ var tmp = this.dfs(target, parent, visited);
+
+ if (tmp != null && model.getGeometry(target) != null)
+ {
+ if (prev == null)
+ {
+ node.child = tmp;
+ }
+ else
+ {
+ prev.next = tmp;
+ }
+
+ prev = tmp;
+ }
+ }
+ }
+ }
+
+ return node;
+};
+
+/**
+ * Function: layout
+ *
+ * Starts the actual compact tree layout algorithm
+ * at the given node.
+ */
+mxCompactTreeLayout.prototype.layout = function(node)
+{
+ if (node != null)
+ {
+ var child = node.child;
+
+ while (child != null)
+ {
+ this.layout(child);
+ child = child.next;
+ }
+
+ if (node.child != null)
+ {
+ this.attachParent(node, this.join(node));
+ }
+ else
+ {
+ this.layoutLeaf(node);
+ }
+ }
+};
+
+/**
+ * Function: horizontalLayout
+ */
+mxCompactTreeLayout.prototype.horizontalLayout = function(node, x0, y0, bounds)
+{
+ node.x += x0 + node.offsetX;
+ node.y += y0 + node.offsetY;
+ bounds = this.apply(node, bounds);
+ var child = node.child;
+
+ if (child != null)
+ {
+ bounds = this.horizontalLayout(child, node.x, node.y, bounds);
+ var siblingOffset = node.y + child.offsetY;
+ var s = child.next;
+
+ while (s != null)
+ {
+ bounds = this.horizontalLayout(s, node.x + child.offsetX, siblingOffset, bounds);
+ siblingOffset += s.offsetY;
+ s = s.next;
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: verticalLayout
+ */
+mxCompactTreeLayout.prototype.verticalLayout = function(node, parent, x0, y0, bounds)
+{
+ node.x += x0 + node.offsetY;
+ node.y += y0 + node.offsetX;
+ bounds = this.apply(node, bounds);
+ var child = node.child;
+
+ if (child != null)
+ {
+ bounds = this.verticalLayout(child, node, node.x, node.y, bounds);
+ var siblingOffset = node.x + child.offsetY;
+ var s = child.next;
+
+ while (s != null)
+ {
+ bounds = this.verticalLayout(s, node, siblingOffset, node.y + child.offsetX, bounds);
+ siblingOffset += s.offsetY;
+ s = s.next;
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: attachParent
+ */
+mxCompactTreeLayout.prototype.attachParent = function(node, height)
+{
+ var x = this.nodeDistance + this.levelDistance;
+ var y2 = (height - node.width) / 2 - this.nodeDistance;
+ var y1 = y2 + node.width + 2 * this.nodeDistance - height;
+
+ node.child.offsetX = x + node.height;
+ node.child.offsetY = y1;
+
+ node.contour.upperHead = this.createLine(node.height, 0,
+ this.createLine(x, y1, node.contour.upperHead));
+ node.contour.lowerHead = this.createLine(node.height, 0,
+ this.createLine(x, y2, node.contour.lowerHead));
+};
+
+/**
+ * Function: layoutLeaf
+ */
+mxCompactTreeLayout.prototype.layoutLeaf = function(node)
+{
+ var dist = 2 * this.nodeDistance;
+
+ node.contour.upperTail = this.createLine(
+ node.height + dist, 0);
+ node.contour.upperHead = node.contour.upperTail;
+ node.contour.lowerTail = this.createLine(
+ 0, -node.width - dist);
+ node.contour.lowerHead = this.createLine(
+ node.height + dist, 0, node.contour.lowerTail);
+};
+
+/**
+ * Function: join
+ */
+mxCompactTreeLayout.prototype.join = function(node)
+{
+ var dist = 2 * this.nodeDistance;
+
+ var child = node.child;
+ node.contour = child.contour;
+ var h = child.width + dist;
+ var sum = h;
+ child = child.next;
+
+ while (child != null)
+ {
+ var d = this.merge(node.contour, child.contour);
+ child.offsetY = d + h;
+ child.offsetX = 0;
+ h = child.width + dist;
+ sum += d + h;
+ child = child.next;
+ }
+
+ return sum;
+};
+
+/**
+ * Function: merge
+ */
+mxCompactTreeLayout.prototype.merge = function(p1, p2)
+{
+ var x = 0;
+ var y = 0;
+ var total = 0;
+
+ var upper = p1.lowerHead;
+ var lower = p2.upperHead;
+
+ while (lower != null && upper != null)
+ {
+ var d = this.offset(x, y, lower.dx, lower.dy,
+ upper.dx, upper.dy);
+ y += d;
+ total += d;
+
+ if (x + lower.dx <= upper.dx)
+ {
+ x += lower.dx;
+ y += lower.dy;
+ lower = lower.next;
+ }
+ else
+ {
+ x -= upper.dx;
+ y -= upper.dy;
+ upper = upper.next;
+ }
+ }
+
+ if (lower != null)
+ {
+ var b = this.bridge(p1.upperTail, 0, 0, lower, x, y);
+ p1.upperTail = (b.next != null) ? p2.upperTail : b;
+ p1.lowerTail = p2.lowerTail;
+ }
+ else
+ {
+ var b = this.bridge(p2.lowerTail, x, y, upper, 0, 0);
+
+ if (b.next == null)
+ {
+ p1.lowerTail = b;
+ }
+ }
+
+ p1.lowerHead = p2.lowerHead;
+
+ return total;
+};
+
+/**
+ * Function: offset
+ */
+mxCompactTreeLayout.prototype.offset = function(p1, p2, a1, a2, b1, b2)
+{
+ var d = 0;
+
+ if (b1 <= p1 || p1 + a1 <= 0)
+ {
+ return 0;
+ }
+
+ var t = b1 * a2 - a1 * b2;
+
+ if (t > 0)
+ {
+ if (p1 < 0)
+ {
+ var s = p1 * a2;
+ d = s / a1 - p2;
+ }
+ else if (p1 > 0)
+ {
+ var s = p1 * b2;
+ d = s / b1 - p2;
+ }
+ else
+ {
+ d = -p2;
+ }
+ }
+ else if (b1 < p1 + a1)
+ {
+ var s = (b1 - p1) * a2;
+ d = b2 - (p2 + s / a1);
+ }
+ else if (b1 > p1 + a1)
+ {
+ var s = (a1 + p1) * b2;
+ d = s / b1 - (p2 + a2);
+ }
+ else
+ {
+ d = b2 - (p2 + a2);
+ }
+
+ if (d > 0)
+ {
+ return d;
+ }
+ else
+ {
+ return 0;
+ }
+};
+
+/**
+ * Function: bridge
+ */
+mxCompactTreeLayout.prototype.bridge = function(line1, x1, y1, line2, x2, y2)
+{
+ var dx = x2 + line2.dx - x1;
+ var dy = 0;
+ var s = 0;
+
+ if (line2.dx == 0)
+ {
+ dy = line2.dy;
+ }
+ else
+ {
+ s = dx * line2.dy;
+ dy = s / line2.dx;
+ }
+
+ var r = this.createLine(dx, dy, line2.next);
+ line1.next = this.createLine(0, y2 + line2.dy - dy - y1, r);
+
+ return r;
+};
+
+/**
+ * Function: createNode
+ */
+mxCompactTreeLayout.prototype.createNode = function(cell)
+{
+ var node = new Object();
+ node.cell = cell;
+ node.x = 0;
+ node.y = 0;
+ node.width = 0;
+ node.height = 0;
+
+ var geo = this.getVertexBounds(cell);
+
+ if (geo != null)
+ {
+ if (this.isHorizontal())
+ {
+ node.width = geo.height;
+ node.height = geo.width;
+ }
+ else
+ {
+ node.width = geo.width;
+ node.height = geo.height;
+ }
+ }
+
+ node.offsetX = 0;
+ node.offsetY = 0;
+ node.contour = new Object();
+
+ return node;
+};
+
+/**
+ * Function: apply
+ */
+mxCompactTreeLayout.prototype.apply = function(node, bounds)
+{
+ var model = this.graph.getModel();
+ var cell = node.cell;
+ var g = model.getGeometry(cell);
+
+ if (cell != null && g != null)
+ {
+ if (this.isVertexMovable(cell))
+ {
+ g = this.setVertexLocation(cell, node.x, node.y);
+
+ if (this.resizeParent)
+ {
+ var parent = model.getParent(cell);
+ var id = mxCellPath.create(parent);
+
+ // Implements set semantic
+ if (this.parentsChanged[id] == null)
+ {
+ this.parentsChanged[id] = parent;
+ }
+ }
+ }
+
+ if (bounds == null)
+ {
+ bounds = new mxRectangle(g.x, g.y, g.width, g.height);
+ }
+ else
+ {
+ bounds = new mxRectangle(Math.min(bounds.x, g.x),
+ Math.min(bounds.y, g.y),
+ Math.max(bounds.x + bounds.width, g.x + g.width),
+ Math.max(bounds.y + bounds.height, g.y + g.height));
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: createLine
+ */
+mxCompactTreeLayout.prototype.createLine = function(dx, dy, next)
+{
+ var line = new Object();
+ line.dx = dx;
+ line.dy = dy;
+ line.next = next;
+
+ return line;
+};
+
+/**
+ * Function: adjustParents
+ *
+ * Adjust parent cells whose child geometries have changed. The default
+ * implementation adjusts the group to just fit around the children with
+ * a padding.
+ */
+mxCompactTreeLayout.prototype.adjustParents = function()
+{
+ var tmp = [];
+
+ for (var id in this.parentsChanged)
+ {
+ tmp.push(this.parentsChanged[id]);
+ }
+
+ this.arrangeGroups(mxUtils.sortCells(tmp, true), this.groupPadding);
+};
+
+/**
+ * Function: localEdgeProcessing
+ *
+ * Moves the specified node and all of its children by the given amount.
+ */
+mxCompactTreeLayout.prototype.localEdgeProcessing = function(node)
+{
+ this.processNodeOutgoing(node);
+ var child = node.child;
+
+ while (child != null)
+ {
+ this.localEdgeProcessing(child);
+ child = child.next;
+ }
+};
+
+/**
+ * Function: localEdgeProcessing
+ *
+ * Separates the x position of edges as they connect to vertices
+ */
+mxCompactTreeLayout.prototype.processNodeOutgoing = function(node)
+{
+ var child = node.child;
+ var parentCell = node.cell;
+
+ var childCount = 0;
+ var sortedCells = [];
+
+ while (child != null)
+ {
+ childCount++;
+
+ var sortingCriterion = child.x;
+
+ if (this.horizontal)
+ {
+ sortingCriterion = child.y;
+ }
+
+ sortedCells.push(new WeightedCellSorter(child, sortingCriterion));
+ child = child.next;
+ }
+
+ sortedCells.sort(WeightedCellSorter.prototype.compare);
+
+ var availableWidth = node.width;
+
+ var requiredWidth = (childCount + 1) * this.prefHozEdgeSep;
+
+ // Add a buffer on the edges of the vertex if the edge count allows
+ if (availableWidth > requiredWidth + (2 * this.prefHozEdgeSep))
+ {
+ availableWidth -= 2 * this.prefHozEdgeSep;
+ }
+
+ var edgeSpacing = availableWidth / childCount;
+
+ var currentXOffset = edgeSpacing / 2.0;
+
+ if (availableWidth > requiredWidth + (2 * this.prefHozEdgeSep))
+ {
+ currentXOffset += this.prefHozEdgeSep;
+ }
+
+ var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
+ var maxYOffset = 0;
+
+ var parentBounds = this.getVertexBounds(parentCell);
+ child = node.child;
+
+ for (var j = 0; j < sortedCells.length; j++)
+ {
+ var childCell = sortedCells[j].cell.cell;
+ var childBounds = this.getVertexBounds(childCell);
+
+ var edges = this.graph.getEdgesBetween(parentCell,
+ childCell, false);
+
+ var newPoints = [];
+ var x = 0;
+ var y = 0;
+
+ for (var i = 0; i < edges.length; i++)
+ {
+ if (this.horizontal)
+ {
+ // Use opposite co-ords, calculation was done for
+ //
+ x = parentBounds.x + parentBounds.width;
+ y = parentBounds.y + currentXOffset;
+ newPoints.push(new mxPoint(x, y));
+ x = parentBounds.x + parentBounds.width
+ + currentYOffset;
+ newPoints.push(new mxPoint(x, y));
+ y = childBounds.y + childBounds.height / 2.0;
+ newPoints.push(new mxPoint(x, y));
+ this.setEdgePoints(edges[i], newPoints);
+ }
+ else
+ {
+ x = parentBounds.x + currentXOffset;
+ y = parentBounds.y + parentBounds.height;
+ newPoints.push(new mxPoint(x, y));
+ y = parentBounds.y + parentBounds.height
+ + currentYOffset;
+ newPoints.push(new mxPoint(x, y));
+ x = childBounds.x + childBounds.width / 2.0;
+ newPoints.push(new mxPoint(x, y));
+ this.setEdgePoints(edges[i], newPoints);
+ }
+ }
+
+ if (j < childCount / 2)
+ {
+ currentYOffset += this.prefVertEdgeOff;
+ }
+ else if (j > childCount / 2)
+ {
+ currentYOffset -= this.prefVertEdgeOff;
+ }
+ // Ignore the case if equals, this means the second of 2
+ // jettys with the same y (even number of edges)
+
+ // pos[k * 2] = currentX;
+ currentXOffset += edgeSpacing;
+ // pos[k * 2 + 1] = currentYOffset;
+
+ maxYOffset = Math.max(maxYOffset, currentYOffset);
+ }
+};
+
+/**
+ * Class: WeightedCellSorter
+ *
+ * A utility class used to track cells whilst sorting occurs on the weighted
+ * sum of their connected edges. Does not violate (x.compareTo(y)==0) ==
+ * (x.equals(y))
+ *
+ * Constructor: WeightedCellSorter
+ *
+ * Constructs a new weighted cell sorted for the given cell and weight.
+ */
+function WeightedCellSorter(cell, weightedValue)
+{
+ this.cell = cell;
+ this.weightedValue = weightedValue;
+};
+
+/**
+ * Variable: weightedValue
+ *
+ * The weighted value of the cell stored.
+ */
+WeightedCellSorter.prototype.weightedValue = 0;
+
+/**
+ * Variable: nudge
+ *
+ * Whether or not to flip equal weight values.
+ */
+WeightedCellSorter.prototype.nudge = false;
+
+/**
+ * Variable: visited
+ *
+ * Whether or not this cell has been visited in the current assignment.
+ */
+WeightedCellSorter.prototype.visited = false;
+
+/**
+ * Variable: rankIndex
+ *
+ * The index this cell is in the model rank.
+ */
+WeightedCellSorter.prototype.rankIndex = null;
+
+/**
+ * Variable: cell
+ *
+ * The cell whose median value is being calculated.
+ */
+WeightedCellSorter.prototype.cell = null;
+
+/**
+ * Function: compare
+ *
+ * Compares two WeightedCellSorters.
+ */
+WeightedCellSorter.prototype.compare = function(a, b)
+{
+ if (a != null && b != null)
+ {
+ if (b.weightedValue > a.weightedValue)
+ {
+ return 1;
+ }
+ else if (b.weightedValue < a.weightedValue)
+ {
+ return -1;
+ }
+ else
+ {
+ if (b.nudge)
+ {
+ return 1;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ }
+ else
+ {
+ return 0;
+ }
+}; \ No newline at end of file
diff --git a/src/js/layout/mxCompositeLayout.js b/src/js/layout/mxCompositeLayout.js
new file mode 100644
index 0000000..2ceb5f5
--- /dev/null
+++ b/src/js/layout/mxCompositeLayout.js
@@ -0,0 +1,101 @@
+/**
+ * $Id: mxCompositeLayout.js,v 1.11 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCompositeLayout
+ *
+ * Allows to compose multiple layouts into a single layout. The master layout
+ * is the layout that handles move operations if another layout than the first
+ * element in <layouts> should be used. The <master> layout is not executed as
+ * the code assumes that it is part of <layouts>.
+ *
+ * Example:
+ * (code)
+ * var first = new mxFastOrganicLayout(graph);
+ * var second = new mxParallelEdgeLayout(graph);
+ * var layout = new mxCompositeLayout(graph, [first, second], first);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxCompositeLayout
+ *
+ * Constructs a new layout using the given layouts. The graph instance is
+ * required for creating the transaction that contains all layouts.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * layouts - Array of <mxGraphLayouts>.
+ * master - Optional layout that handles moves. If no layout is given then
+ * the first layout of the above array is used to handle moves.
+ */
+function mxCompositeLayout(graph, layouts, master)
+{
+ mxGraphLayout.call(this, graph);
+ this.layouts = layouts;
+ this.master = master;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCompositeLayout.prototype = new mxGraphLayout();
+mxCompositeLayout.prototype.constructor = mxCompositeLayout;
+
+/**
+ * Variable: layouts
+ *
+ * Holds the array of <mxGraphLayouts> that this layout contains.
+ */
+mxCompositeLayout.prototype.layouts = null;
+
+/**
+ * Variable: layouts
+ *
+ * Reference to the <mxGraphLayouts> that handles moves. If this is null
+ * then the first layout in <layouts> is used.
+ */
+mxCompositeLayout.prototype.master = null;
+
+/**
+ * Function: moveCell
+ *
+ * Implements <mxGraphLayout.moveCell> by calling move on <master> or the first
+ * layout in <layouts>.
+ */
+mxCompositeLayout.prototype.moveCell = function(cell, x, y)
+{
+ if (this.master != null)
+ {
+ this.master.move.apply(this.master, arguments);
+ }
+ else
+ {
+ this.layouts[0].move.apply(this.layouts[0], arguments);
+ }
+};
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute> by executing all <layouts> in a
+ * single transaction.
+ */
+mxCompositeLayout.prototype.execute = function(parent)
+{
+ var model = this.graph.getModel();
+
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < this.layouts.length; i++)
+ {
+ this.layouts[i].execute.apply(this.layouts[i], arguments);
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
diff --git a/src/js/layout/mxEdgeLabelLayout.js b/src/js/layout/mxEdgeLabelLayout.js
new file mode 100644
index 0000000..2bfb3c2
--- /dev/null
+++ b/src/js/layout/mxEdgeLabelLayout.js
@@ -0,0 +1,165 @@
+/**
+ * $Id: mxEdgeLabelLayout.js,v 1.8 2010-01-04 11:18:25 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEdgeLabelLayout
+ *
+ * Extends <mxGraphLayout> to implement an edge label layout. This layout
+ * makes use of cell states, which means the graph must be validated in
+ * a graph view (so that the label bounds are available) before this layout
+ * can be executed.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxEdgeLabelLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxEdgeLabelLayout
+ *
+ * Constructs a new edge label layout.
+ *
+ * Arguments:
+ *
+ * graph - <mxGraph> that contains the cells.
+ */
+function mxEdgeLabelLayout(graph, radius)
+{
+ mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxEdgeLabelLayout.prototype = new mxGraphLayout();
+mxEdgeLabelLayout.prototype.constructor = mxEdgeLabelLayout;
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>.
+ */
+mxEdgeLabelLayout.prototype.execute = function(parent)
+{
+ var view = this.graph.view;
+ var model = this.graph.getModel();
+
+ // Gets all vertices and edges inside the parent
+ var edges = [];
+ var vertices = [];
+ var childCount = model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cell = model.getChildAt(parent, i);
+ var state = view.getState(cell);
+
+ if (state != null)
+ {
+ if (!this.isVertexIgnored(cell))
+ {
+ vertices.push(state);
+ }
+ else if (!this.isEdgeIgnored(cell))
+ {
+ edges.push(state);
+ }
+ }
+ }
+
+ this.placeLabels(vertices, edges);
+};
+
+/**
+ * Function: placeLabels
+ *
+ * Places the labels of the given edges.
+ */
+mxEdgeLabelLayout.prototype.placeLabels = function(v, e)
+{
+ var model = this.graph.getModel();
+
+ // Moves the vertices to build a circle. Makes sure the
+ // radius is large enough for the vertices to not
+ // overlap
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < e.length; i++)
+ {
+ var edge = e[i];
+
+ if (edge != null && edge.text != null &&
+ edge.text.boundingBox != null)
+ {
+ for (var j = 0; j < v.length; j++)
+ {
+ var vertex = v[j];
+
+ if (vertex != null)
+ {
+ this.avoid(edge, vertex);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: avoid
+ *
+ * Places the labels of the given edges.
+ */
+mxEdgeLabelLayout.prototype.avoid = function(edge, vertex)
+{
+ var model = this.graph.getModel();
+ var labRect = edge.text.boundingBox;
+
+ if (mxUtils.intersects(labRect, vertex))
+ {
+ var dy1 = -labRect.y - labRect.height + vertex.y;
+ var dy2 = -labRect.y + vertex.y + vertex.height;
+
+ var dy = (Math.abs(dy1) < Math.abs(dy2)) ? dy1 : dy2;
+
+ var dx1 = -labRect.x - labRect.width + vertex.x;
+ var dx2 = -labRect.x + vertex.x + vertex.width;
+
+ var dx = (Math.abs(dx1) < Math.abs(dx2)) ? dx1 : dx2;
+
+ if (Math.abs(dx) < Math.abs(dy))
+ {
+ dy = 0;
+ }
+ else
+ {
+ dx = 0;
+ }
+
+ var g = model.getGeometry(edge.cell);
+
+ if (g != null)
+ {
+ g = g.clone();
+
+ if (g.offset != null)
+ {
+ g.offset.x += dx;
+ g.offset.y += dy;
+ }
+ else
+ {
+ g.offset = new mxPoint(dx, dy);
+ }
+
+ model.setGeometry(edge.cell, g);
+ }
+ }
+};
diff --git a/src/js/layout/mxFastOrganicLayout.js b/src/js/layout/mxFastOrganicLayout.js
new file mode 100644
index 0000000..d7d6b5d
--- /dev/null
+++ b/src/js/layout/mxFastOrganicLayout.js
@@ -0,0 +1,591 @@
+/**
+ * $Id: mxFastOrganicLayout.js,v 1.37 2011-04-28 13:14:55 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxFastOrganicLayout
+ *
+ * Extends <mxGraphLayout> to implement a fast organic layout algorithm.
+ * The vertices need to be connected for this layout to work, vertices
+ * with no connections are ignored.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxFastOrganicLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxCompactTreeLayout
+ *
+ * Constructs a new fast organic layout for the specified graph.
+ */
+function mxFastOrganicLayout(graph)
+{
+ mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxFastOrganicLayout.prototype = new mxGraphLayout();
+mxFastOrganicLayout.prototype.constructor = mxFastOrganicLayout;
+
+/**
+ * Variable: useInputOrigin
+ *
+ * Specifies if the top left corner of the input cells should be the origin
+ * of the layout result. Default is true.
+ */
+mxFastOrganicLayout.prototype.useInputOrigin = true;
+
+/**
+ * Variable: resetEdges
+ *
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxFastOrganicLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: disableEdgeStyle
+ *
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxFastOrganicLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: forceConstant
+ *
+ * The force constant by which the attractive forces are divided and the
+ * replusive forces are multiple by the square of. The value equates to the
+ * average radius there is of free space around each node. Default is 50.
+ */
+mxFastOrganicLayout.prototype.forceConstant = 50;
+
+/**
+ * Variable: forceConstantSquared
+ *
+ * Cache of <forceConstant>^2 for performance.
+ */
+mxFastOrganicLayout.prototype.forceConstantSquared = 0;
+
+/**
+ * Variable: minDistanceLimit
+ *
+ * Minimal distance limit. Default is 2. Prevents of
+ * dividing by zero.
+ */
+mxFastOrganicLayout.prototype.minDistanceLimit = 2;
+
+/**
+ * Variable: minDistanceLimit
+ *
+ * Minimal distance limit. Default is 2. Prevents of
+ * dividing by zero.
+ */
+mxFastOrganicLayout.prototype.maxDistanceLimit = 500;
+
+/**
+ * Variable: minDistanceLimitSquared
+ *
+ * Cached version of <minDistanceLimit> squared.
+ */
+mxFastOrganicLayout.prototype.minDistanceLimitSquared = 4;
+
+/**
+ * Variable: initialTemp
+ *
+ * Start value of temperature. Default is 200.
+ */
+mxFastOrganicLayout.prototype.initialTemp = 200;
+
+/**
+ * Variable: temperature
+ *
+ * Temperature to limit displacement at later stages of layout.
+ */
+mxFastOrganicLayout.prototype.temperature = 0;
+
+/**
+ * Variable: maxIterations
+ *
+ * Total number of iterations to run the layout though.
+ */
+mxFastOrganicLayout.prototype.maxIterations = 0;
+
+/**
+ * Variable: iteration
+ *
+ * Current iteration count.
+ */
+mxFastOrganicLayout.prototype.iteration = 0;
+
+/**
+ * Variable: vertexArray
+ *
+ * An array of all vertices to be laid out.
+ */
+mxFastOrganicLayout.prototype.vertexArray;
+
+/**
+ * Variable: dispX
+ *
+ * An array of locally stored X co-ordinate displacements for the vertices.
+ */
+mxFastOrganicLayout.prototype.dispX;
+
+/**
+ * Variable: dispY
+ *
+ * An array of locally stored Y co-ordinate displacements for the vertices.
+ */
+mxFastOrganicLayout.prototype.dispY;
+
+/**
+ * Variable: cellLocation
+ *
+ * An array of locally stored co-ordinate positions for the vertices.
+ */
+mxFastOrganicLayout.prototype.cellLocation;
+
+/**
+ * Variable: radius
+ *
+ * The approximate radius of each cell, nodes only.
+ */
+mxFastOrganicLayout.prototype.radius;
+
+/**
+ * Variable: radiusSquared
+ *
+ * The approximate radius squared of each cell, nodes only.
+ */
+mxFastOrganicLayout.prototype.radiusSquared;
+
+/**
+ * Variable: isMoveable
+ *
+ * Array of booleans representing the movable states of the vertices.
+ */
+mxFastOrganicLayout.prototype.isMoveable;
+
+/**
+ * Variable: neighbours
+ *
+ * Local copy of cell neighbours.
+ */
+mxFastOrganicLayout.prototype.neighbours;
+
+/**
+ * Variable: indices
+ *
+ * Hashtable from cells to local indices.
+ */
+mxFastOrganicLayout.prototype.indices;
+
+/**
+ * Variable: allowedToRun
+ *
+ * Boolean flag that specifies if the layout is allowed to run. If this is
+ * set to false, then the layout exits in the following iteration.
+ */
+mxFastOrganicLayout.prototype.allowedToRun = true;
+
+/**
+ * Function: isVertexIgnored
+ *
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxFastOrganicLayout.prototype.isVertexIgnored = function(vertex)
+{
+ return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+ this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>. This operates on all children of the
+ * given parent where <isVertexIgnored> returns false.
+ */
+mxFastOrganicLayout.prototype.execute = function(parent)
+{
+ var model = this.graph.getModel();
+ this.vertexArray = [];
+ var cells = this.graph.getChildVertices(parent);
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isVertexIgnored(cells[i]))
+ {
+ this.vertexArray.push(cells[i]);
+ }
+ }
+
+ var initialBounds = (this.useInputOrigin) ?
+ this.graph.view.getBounds(this.vertexArray) :
+ null;
+ var n = this.vertexArray.length;
+
+ this.indices = [];
+ this.dispX = [];
+ this.dispY = [];
+ this.cellLocation = [];
+ this.isMoveable = [];
+ this.neighbours = [];
+ this.radius = [];
+ this.radiusSquared = [];
+
+ if (this.forceConstant < 0.001)
+ {
+ this.forceConstant = 0.001;
+ }
+
+ this.forceConstantSquared = this.forceConstant * this.forceConstant;
+
+ // Create a map of vertices first. This is required for the array of
+ // arrays called neighbours which holds, for each vertex, a list of
+ // ints which represents the neighbours cells to that vertex as
+ // the indices into vertexArray
+ for (var i = 0; i < this.vertexArray.length; i++)
+ {
+ var vertex = this.vertexArray[i];
+ this.cellLocation[i] = [];
+
+ // Set up the mapping from array indices to cells
+ var id = mxCellPath.create(vertex);
+ this.indices[id] = i;
+ var bounds = this.getVertexBounds(vertex);
+
+ // Set the X,Y value of the internal version of the cell to
+ // the center point of the vertex for better positioning
+ var width = bounds.width;
+ var height = bounds.height;
+
+ // Randomize (0, 0) locations
+ var x = bounds.x;
+ var y = bounds.y;
+
+ this.cellLocation[i][0] = x + width / 2.0;
+ this.cellLocation[i][1] = y + height / 2.0;
+ this.radius[i] = Math.min(width, height);
+ this.radiusSquared[i] = this.radius[i] * this.radius[i];
+ }
+
+ // Moves cell location back to top-left from center locations used in
+ // algorithm, resetting the edge points is part of the transaction
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < n; i++)
+ {
+ this.dispX[i] = 0;
+ this.dispY[i] = 0;
+ this.isMoveable[i] = this.isVertexMovable(this.vertexArray[i]);
+
+ // Get lists of neighbours to all vertices, translate the cells
+ // obtained in indices into vertexArray and store as an array
+ // against the orginial cell index
+ var edges = this.graph.getConnections(this.vertexArray[i], parent);
+ var cells = this.graph.getOpposites(edges, this.vertexArray[i]);
+ this.neighbours[i] = [];
+
+ for (var j = 0; j < cells.length; j++)
+ {
+ // Resets the points on the traversed edge
+ if (this.resetEdges)
+ {
+ this.graph.resetEdge(edges[j]);
+ }
+
+ if (this.disableEdgeStyle)
+ {
+ this.setEdgeStyleEnabled(edges[j], false);
+ }
+
+ // Looks the cell up in the indices dictionary
+ var id = mxCellPath.create(cells[j]);
+ var index = this.indices[id];
+
+ // Check the connected cell in part of the vertex list to be
+ // acted on by this layout
+ if (index != null)
+ {
+ this.neighbours[i][j] = index;
+ }
+
+ // Else if index of the other cell doesn't correspond to
+ // any cell listed to be acted upon in this layout. Set
+ // the index to the value of this vertex (a dummy self-loop)
+ // so the attraction force of the edge is not calculated
+ else
+ {
+ this.neighbours[i][j] = i;
+ }
+ }
+ }
+ this.temperature = this.initialTemp;
+
+ // If max number of iterations has not been set, guess it
+ if (this.maxIterations == 0)
+ {
+ this.maxIterations = 20 * Math.sqrt(n);
+ }
+
+ // Main iteration loop
+ for (this.iteration = 0; this.iteration < this.maxIterations; this.iteration++)
+ {
+ if (!this.allowedToRun)
+ {
+ return;
+ }
+
+ // Calculate repulsive forces on all vertices
+ this.calcRepulsion();
+
+ // Calculate attractive forces through edges
+ this.calcAttraction();
+
+ this.calcPositions();
+ this.reduceTemperature();
+ }
+
+ var minx = null;
+ var miny = null;
+
+ for (var i = 0; i < this.vertexArray.length; i++)
+ {
+ var vertex = this.vertexArray[i];
+
+ if (this.isVertexMovable(vertex))
+ {
+ var bounds = this.getVertexBounds(vertex);
+
+ if (bounds != null)
+ {
+ this.cellLocation[i][0] -= bounds.width / 2.0;
+ this.cellLocation[i][1] -= bounds.height / 2.0;
+
+ var x = this.graph.snap(this.cellLocation[i][0]);
+ var y = this.graph.snap(this.cellLocation[i][1]);
+
+ this.setVertexLocation(vertex, x, y);
+
+ if (minx == null)
+ {
+ minx = x;
+ }
+ else
+ {
+ minx = Math.min(minx, x);
+ }
+
+ if (miny == null)
+ {
+ miny = y;
+ }
+ else
+ {
+ miny = Math.min(miny, y);
+ }
+ }
+ }
+ }
+
+ // Modifies the cloned geometries in-place. Not needed
+ // to clone the geometries again as we're in the same
+ // undoable change.
+ var dx = -(minx || 0) + 1;
+ var dy = -(miny || 0) + 1;
+
+ if (initialBounds != null)
+ {
+ dx += initialBounds.x;
+ dy += initialBounds.y;
+ }
+
+ this.graph.moveCells(this.vertexArray, dx, dy);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: calcPositions
+ *
+ * Takes the displacements calculated for each cell and applies them to the
+ * local cache of cell positions. Limits the displacement to the current
+ * temperature.
+ */
+mxFastOrganicLayout.prototype.calcPositions = function()
+{
+ for (var index = 0; index < this.vertexArray.length; index++)
+ {
+ if (this.isMoveable[index])
+ {
+ // Get the distance of displacement for this node for this
+ // iteration
+ var deltaLength = Math.sqrt(this.dispX[index] * this.dispX[index] +
+ this.dispY[index] * this.dispY[index]);
+
+ if (deltaLength < 0.001)
+ {
+ deltaLength = 0.001;
+ }
+
+ // Scale down by the current temperature if less than the
+ // displacement distance
+ var newXDisp = this.dispX[index] / deltaLength
+ * Math.min(deltaLength, this.temperature);
+
+ var newYDisp = this.dispY[index] / deltaLength
+ * Math.min(deltaLength, this.temperature);
+
+ // reset displacements
+ this.dispX[index] = 0;
+ this.dispY[index] = 0;
+
+ // Update the cached cell locations
+ this.cellLocation[index][0] += newXDisp;
+ this.cellLocation[index][1] += newYDisp;
+ }
+ }
+};
+
+/**
+ * Function: calcAttraction
+ *
+ * Calculates the attractive forces between all laid out nodes linked by
+ * edges
+ */
+mxFastOrganicLayout.prototype.calcAttraction = function()
+{
+ // Check the neighbours of each vertex and calculate the attractive
+ // force of the edge connecting them
+ for (var i = 0; i < this.vertexArray.length; i++)
+ {
+ for (var k = 0; k < this.neighbours[i].length; k++)
+ {
+ // Get the index of the othe cell in the vertex array
+ var j = this.neighbours[i][k];
+
+ // Do not proceed self-loops
+ if (i != j &&
+ this.isMoveable[i] &&
+ this.isMoveable[j])
+ {
+ var xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];
+ var yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];
+
+ // The distance between the nodes
+ var deltaLengthSquared = xDelta * xDelta + yDelta
+ * yDelta - this.radiusSquared[i] - this.radiusSquared[j];
+
+ if (deltaLengthSquared < this.minDistanceLimitSquared)
+ {
+ deltaLengthSquared = this.minDistanceLimitSquared;
+ }
+
+ var deltaLength = Math.sqrt(deltaLengthSquared);
+ var force = (deltaLengthSquared) / this.forceConstant;
+
+ var displacementX = (xDelta / deltaLength) * force;
+ var displacementY = (yDelta / deltaLength) * force;
+
+ this.dispX[i] -= displacementX;
+ this.dispY[i] -= displacementY;
+
+ this.dispX[j] += displacementX;
+ this.dispY[j] += displacementY;
+ }
+ }
+ }
+};
+
+/**
+ * Function: calcRepulsion
+ *
+ * Calculates the repulsive forces between all laid out nodes
+ */
+mxFastOrganicLayout.prototype.calcRepulsion = function()
+{
+ var vertexCount = this.vertexArray.length;
+
+ for (var i = 0; i < vertexCount; i++)
+ {
+ for (var j = i; j < vertexCount; j++)
+ {
+ // Exits if the layout is no longer allowed to run
+ if (!this.allowedToRun)
+ {
+ return;
+ }
+
+ if (j != i &&
+ this.isMoveable[i] &&
+ this.isMoveable[j])
+ {
+ var xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];
+ var yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];
+
+ if (xDelta == 0)
+ {
+ xDelta = 0.01 + Math.random();
+ }
+
+ if (yDelta == 0)
+ {
+ yDelta = 0.01 + Math.random();
+ }
+
+ // Distance between nodes
+ var deltaLength = Math.sqrt((xDelta * xDelta)
+ + (yDelta * yDelta));
+ var deltaLengthWithRadius = deltaLength - this.radius[i]
+ - this.radius[j];
+
+ if (deltaLengthWithRadius > this.maxDistanceLimit)
+ {
+ // Ignore vertices too far apart
+ continue;
+ }
+
+ if (deltaLengthWithRadius < this.minDistanceLimit)
+ {
+ deltaLengthWithRadius = this.minDistanceLimit;
+ }
+
+ var force = this.forceConstantSquared / deltaLengthWithRadius;
+
+ var displacementX = (xDelta / deltaLength) * force;
+ var displacementY = (yDelta / deltaLength) * force;
+
+ this.dispX[i] += displacementX;
+ this.dispY[i] += displacementY;
+
+ this.dispX[j] -= displacementX;
+ this.dispY[j] -= displacementY;
+ }
+ }
+ }
+};
+
+/**
+ * Function: reduceTemperature
+ *
+ * Reduces the temperature of the layout from an initial setting in a linear
+ * fashion to zero.
+ */
+mxFastOrganicLayout.prototype.reduceTemperature = function()
+{
+ this.temperature = this.initialTemp * (1.0 - this.iteration / this.maxIterations);
+};
diff --git a/src/js/layout/mxGraphLayout.js b/src/js/layout/mxGraphLayout.js
new file mode 100644
index 0000000..c9f5f32
--- /dev/null
+++ b/src/js/layout/mxGraphLayout.js
@@ -0,0 +1,503 @@
+/**
+ * $Id: mxGraphLayout.js,v 1.48 2012-08-21 17:22:21 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphLayout
+ *
+ * Base class for all layout algorithms in mxGraph. Main public functions are
+ * <move> for handling a moved cell within a layouted parent, and <execute> for
+ * running the layout on a given parent cell.
+ *
+ * Known Subclasses:
+ *
+ * <mxCircleLayout>, <mxCompactTreeLayout>, <mxCompositeLayout>,
+ * <mxFastOrganicLayout>, <mxParallelEdgeLayout>, <mxPartitionLayout>,
+ * <mxStackLayout>
+ *
+ * Constructor: mxGraphLayout
+ *
+ * Constructs a new layout using the given layouts.
+ *
+ * Arguments:
+ *
+ * graph - Enclosing
+ */
+function mxGraphLayout(graph)
+{
+ this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphLayout.prototype.graph = null;
+
+/**
+ * Variable: useBoundingBox
+ *
+ * Boolean indicating if the bounding box of the label should be used if
+ * its available. Default is true.
+ */
+mxGraphLayout.prototype.useBoundingBox = true;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell of the layout, if any
+ */
+mxGraphLayout.prototype.parent = null;
+
+/**
+ * Function: moveCell
+ *
+ * Notified when a cell is being moved in a parent that has automatic
+ * layout to update the cell state (eg. index) so that the outcome of the
+ * layout will position the vertex as close to the point (x, y) as
+ * possible.
+ *
+ * Empty implementation.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> which has been moved.
+ * x - X-coordinate of the new cell location.
+ * y - Y-coordinate of the new cell location.
+ */
+mxGraphLayout.prototype.moveCell = function(cell, x, y) { };
+
+/**
+ * Function: execute
+ *
+ * Executes the layout algorithm for the children of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be layed out.
+ */
+mxGraphLayout.prototype.execute = function(parent) { };
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this layout operates on.
+ */
+mxGraphLayout.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: getConstraint
+ *
+ * Returns the constraint for the given key and cell. The optional edge and
+ * source arguments are used to return inbound and outgoing routing-
+ * constraints for the given edge and vertex. This implementation always
+ * returns the value for the given key in the style of the given cell.
+ *
+ * Parameters:
+ *
+ * key - Key of the constraint to be returned.
+ * cell - <mxCell> whose constraint should be returned.
+ * edge - Optional <mxCell> that represents the connection whose constraint
+ * should be returned. Default is null.
+ * source - Optional boolean that specifies if the connection is incoming
+ * or outgoing. Default is null.
+ */
+mxGraphLayout.prototype.getConstraint = function(key, cell, edge, source)
+{
+ var state = this.graph.view.getState(cell);
+ var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+
+ return (style != null) ? style[key] : null;
+};
+
+/**
+ * Function: traverse
+ *
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ * mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional array of cell paths for the visited cells.
+ */
+mxGraphLayout.traverse = function(vertex, directed, func, edge, visited)
+{
+ if (func != null && vertex != null)
+ {
+ directed = (directed != null) ? directed : true;
+ visited = visited || [];
+ var id = mxCellPath.create(vertex);
+
+ if (visited[id] == null)
+ {
+ visited[id] = vertex;
+ var result = func(vertex, edge);
+
+ if (result == null || result)
+ {
+ var edgeCount = this.graph.model.getEdgeCount(vertex);
+
+ if (edgeCount > 0)
+ {
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var e = this.graph.model.getEdgeAt(vertex, i);
+ var isSource = this.graph.model.getTerminal(e, true) == vertex;
+
+ if (!directed || isSource)
+ {
+ var next = this.graph.view.getVisibleTerminal(e, !isSource);
+ this.traverse(next, directed, func, e, visited);
+ }
+ }
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: isVertexMovable
+ *
+ * Returns a boolean indicating if the given <mxCell> is movable or
+ * bendable by the algorithm. This implementation returns true if the given
+ * cell is movable in the graph.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraphLayout.prototype.isVertexMovable = function(cell)
+{
+ return this.graph.isCellMovable(cell);
+};
+
+/**
+ * Function: isVertexIgnored
+ *
+ * Returns a boolean indicating if the given <mxCell> should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxGraphLayout.prototype.isVertexIgnored = function(vertex)
+{
+ return !this.graph.getModel().isVertex(vertex) ||
+ !this.graph.isCellVisible(vertex);
+};
+
+/**
+ * Function: isEdgeIgnored
+ *
+ * Returns a boolean indicating if the given <mxCell> should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose ignored state should be returned.
+ */
+mxGraphLayout.prototype.isEdgeIgnored = function(edge)
+{
+ var model = this.graph.getModel();
+
+ return !model.isEdge(edge) ||
+ !this.graph.isCellVisible(edge) ||
+ model.getTerminal(edge, true) == null ||
+ model.getTerminal(edge, false) == null;
+};
+
+/**
+ * Function: setEdgeStyleEnabled
+ *
+ * Disables or enables the edge style of the given edge.
+ */
+mxGraphLayout.prototype.setEdgeStyleEnabled = function(edge, value)
+{
+ this.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE,
+ (value) ? '0' : '1', [edge]);
+};
+
+/**
+ * Function: setOrthogonalEdge
+ *
+ * Disables or enables orthogonal end segments of the given edge.
+ */
+mxGraphLayout.prototype.setOrthogonalEdge = function(edge, value)
+{
+ this.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL,
+ (value) ? '1' : '0', [edge]);
+};
+
+/**
+ * Function: getParentOffset
+ *
+ * Determines the offset of the given parent to the parent
+ * of the layout
+ */
+mxGraphLayout.prototype.getParentOffset = function(parent)
+{
+ var result = new mxPoint();
+
+ if (parent != null && parent != this.parent)
+ {
+ var model = this.graph.getModel();
+
+ if (model.isAncestor(this.parent, parent))
+ {
+ var parentGeo = model.getGeometry(parent);
+
+ while (parent != this.parent)
+ {
+ result.x = result.x + parentGeo.x;
+ result.y = result.y + parentGeo.y;
+
+ parent = model.getParent(parent);;
+ parentGeo = model.getGeometry(parent);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: setEdgePoints
+ *
+ * Replaces the array of mxPoints in the geometry of the given edge
+ * with the given array of mxPoints.
+ */
+mxGraphLayout.prototype.setEdgePoints = function(edge, points)
+{
+ if (edge != null)
+ {
+ var model = this.graph.model;
+ var geometry = model.getGeometry(edge);
+
+ if (geometry == null)
+ {
+ geometry = new mxGeometry();
+ geometry.setRelative(true);
+ }
+ else
+ {
+ geometry = geometry.clone();
+ }
+
+ if (this.parent != null && points != null)
+ {
+ var parent = model.getParent(edge);
+
+ var parentOffset = this.getParentOffset(parent);
+
+ for (var i = 0; i < points.length; i++)
+ {
+ points[i].x = points[i].x - parentOffset.x;
+ points[i].y = points[i].y - parentOffset.y;
+ }
+ }
+
+ geometry.points = points;
+ model.setGeometry(edge, geometry);
+ }
+};
+
+/**
+ * Function: setVertexLocation
+ *
+ * Sets the new position of the given cell taking into account the size of
+ * the bounding box if <useBoundingBox> is true. The change is only carried
+ * out if the new location is not equal to the existing location, otherwise
+ * the geometry is not replaced with an updated instance. The new or old
+ * bounds are returned (including overlapping labels).
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose geometry is to be set.
+ * x - Integer that defines the x-coordinate of the new location.
+ * y - Integer that defines the y-coordinate of the new location.
+ */
+mxGraphLayout.prototype.setVertexLocation = function(cell, x, y)
+{
+ var model = this.graph.getModel();
+ var geometry = model.getGeometry(cell);
+ var result = null;
+
+ if (geometry != null)
+ {
+ result = new mxRectangle(x, y, geometry.width, geometry.height);
+
+ // Checks for oversize labels and shifts the result
+ // TODO: Use mxUtils.getStringSize for label bounds
+ if (this.useBoundingBox)
+ {
+ var state = this.graph.getView().getState(cell);
+
+ if (state != null && state.text != null && state.text.boundingBox != null)
+ {
+ var scale = this.graph.getView().scale;
+ var box = state.text.boundingBox;
+
+ if (state.text.boundingBox.x < state.x)
+ {
+ x += (state.x - box.x) / scale;
+ result.width = box.width;
+ }
+
+ if (state.text.boundingBox.y < state.y)
+ {
+ y += (state.y - box.y) / scale;
+ result.height = box.height;
+ }
+ }
+ }
+
+ if (this.parent != null)
+ {
+ var parent = model.getParent(cell);
+
+ if (parent != null && parent != this.parent)
+ {
+ var parentOffset = this.getParentOffset(parent);
+
+ x = x - parentOffset.x;
+ y = y - parentOffset.y;
+ }
+ }
+
+ if (geometry.x != x || geometry.y != y)
+ {
+ geometry = geometry.clone();
+ geometry.x = x;
+ geometry.y = y;
+
+ model.setGeometry(cell, geometry);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getVertexBounds
+ *
+ * Returns an <mxRectangle> that defines the bounds of the given cell or
+ * the bounding box if <useBoundingBox> is true.
+ */
+mxGraphLayout.prototype.getVertexBounds = function(cell)
+{
+ var geo = this.graph.getModel().getGeometry(cell);
+
+ // Checks for oversize label bounding box and corrects
+ // the return value accordingly
+ // TODO: Use mxUtils.getStringSize for label bounds
+ if (this.useBoundingBox)
+ {
+ var state = this.graph.getView().getState(cell);
+
+ if (state != null && state.text != null && state.text.boundingBox != null)
+ {
+ var scale = this.graph.getView().scale;
+ var tmp = state.text.boundingBox;
+
+ var dx0 = Math.max(state.x - tmp.x, 0) / scale;
+ var dy0 = Math.max(state.y - tmp.y, 0) / scale;
+ var dx1 = Math.max((tmp.x + tmp.width) - (state.x + state.width), 0) / scale;
+ var dy1 = Math.max((tmp.y + tmp.height) - (state.y + state.height), 0) / scale;
+
+ geo = new mxRectangle(geo.x - dx0, geo.y - dy0,
+ geo.width + dx0 + dx1, geo.height + dy0 + dy1);
+ }
+ }
+
+ if (this.parent != null)
+ {
+ var parent = this.graph.getModel().getParent(cell);
+ geo = geo.clone();
+
+ if (parent != null && parent != this.parent)
+ {
+ var parentOffset = this.getParentOffset(parent);
+ geo.x = geo.x + parentOffset.x;
+ geo.y = geo.y + parentOffset.y;
+ }
+ }
+
+ return new mxRectangle(geo.x, geo.y, geo.width, geo.height);
+};
+
+/**
+ * Function: arrangeGroups
+ *
+ * Updates the bounds of the given groups to include all children. Call
+ * this with the groups in parent to child order, top-most group first, eg.
+ *
+ * arrangeGroups(graph, mxUtils.sortCells(Arrays.asList(
+ * new Object[] { v1, v3 }), true).toArray(), 10);
+ */
+mxGraphLayout.prototype.arrangeGroups = function(groups, border)
+{
+ this.graph.getModel().beginUpdate();
+ try
+ {
+ for (var i = groups.length - 1; i >= 0; i--)
+ {
+ var group = groups[i];
+ var children = this.graph.getChildVertices(group);
+ var bounds = this.graph.getBoundingBoxFromGeometry(children);
+ var geometry = this.graph.getCellGeometry(group);
+ var left = 0;
+ var top = 0;
+
+ // Adds the size of the title area for swimlanes
+ if (this.graph.isSwimlane(group))
+ {
+ var size = this.graph.getStartSize(group);
+ left = size.width;
+ top = size.height;
+ }
+
+ if (bounds != null && geometry != null)
+ {
+ geometry = geometry.clone();
+ geometry.x = geometry.x + bounds.x - border - left;
+ geometry.y = geometry.y + bounds.y - border - top;
+ geometry.width = bounds.width + 2 * border + left;
+ geometry.height = bounds.height + 2 * border + top;
+ this.graph.getModel().setGeometry(group, geometry);
+ this.graph.moveCells(children, border + left - bounds.x,
+ border + top - bounds.y);
+ }
+ }
+ }
+ finally
+ {
+ this.graph.getModel().endUpdate();
+ }
+};
diff --git a/src/js/layout/mxParallelEdgeLayout.js b/src/js/layout/mxParallelEdgeLayout.js
new file mode 100644
index 0000000..e1ad57c
--- /dev/null
+++ b/src/js/layout/mxParallelEdgeLayout.js
@@ -0,0 +1,198 @@
+/**
+ * $Id: mxParallelEdgeLayout.js,v 1.24 2012-03-27 15:03:34 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxParallelEdgeLayout
+ *
+ * Extends <mxGraphLayout> for arranging parallel edges. This layout works
+ * on edges for all pairs of vertices where there is more than one edge
+ * connecting the latter.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxParallelEdgeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxCompactTreeLayout
+ *
+ * Constructs a new fast organic layout for the specified graph.
+ */
+function mxParallelEdgeLayout(graph)
+{
+ mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxParallelEdgeLayout.prototype = new mxGraphLayout();
+mxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout;
+
+/**
+ * Variable: spacing
+ *
+ * Defines the spacing between the parallels. Default is 20.
+ */
+mxParallelEdgeLayout.prototype.spacing = 20;
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>.
+ */
+mxParallelEdgeLayout.prototype.execute = function(parent)
+{
+ var lookup = this.findParallels(parent);
+
+ this.graph.model.beginUpdate();
+ try
+ {
+ for (var i in lookup)
+ {
+ var parallels = lookup[i];
+
+ if (parallels.length > 1)
+ {
+ this.layout(parallels);
+ }
+ }
+ }
+ finally
+ {
+ this.graph.model.endUpdate();
+ }
+};
+
+/**
+ * Function: findParallels
+ *
+ * Finds the parallel edges in the given parent.
+ */
+mxParallelEdgeLayout.prototype.findParallels = function(parent)
+{
+ var model = this.graph.getModel();
+ var lookup = [];
+ var childCount = model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (!this.isEdgeIgnored(child))
+ {
+ var id = this.getEdgeId(child);
+
+ if (id != null)
+ {
+ if (lookup[id] == null)
+ {
+ lookup[id] = [];
+ }
+
+ lookup[id].push(child);
+ }
+ }
+ }
+
+ return lookup;
+};
+
+/**
+ * Function: getEdgeId
+ *
+ * Returns a unique ID for the given edge. The id is independent of the
+ * edge direction and is built using the visible terminal of the given
+ * edge.
+ */
+mxParallelEdgeLayout.prototype.getEdgeId = function(edge)
+{
+ var view = this.graph.getView();
+
+ var state = view.getState(edge);
+
+ var src = (state != null) ? state.getVisibleTerminal(true) : view.getVisibleTerminal(edge, true);
+ var trg = (state != null) ? state.getVisibleTerminal(false) : view.getVisibleTerminal(edge, false);
+
+ if (src != null && trg != null)
+ {
+ src = mxCellPath.create(src);
+ trg = mxCellPath.create(trg);
+
+ return (src > trg) ? trg+'-'+src : src+'-'+trg;
+ }
+
+ return null;
+};
+
+/**
+ * Function: layout
+ *
+ * Lays out the parallel edges in the given array.
+ */
+mxParallelEdgeLayout.prototype.layout = function(parallels)
+{
+ var edge = parallels[0];
+ var model = this.graph.getModel();
+
+ var src = model.getGeometry(model.getTerminal(edge, true));
+ var trg = model.getGeometry(model.getTerminal(edge, false));
+
+ // Routes multiple loops
+ if (src == trg)
+ {
+ var x0 = src.x + src.width + this.spacing;
+ var y0 = src.y + src.height / 2;
+
+ for (var i = 0; i < parallels.length; i++)
+ {
+ this.route(parallels[i], x0, y0);
+ x0 += this.spacing;
+ }
+ }
+ else if (src != null && trg != null)
+ {
+ // Routes parallel edges
+ var scx = src.x + src.width / 2;
+ var scy = src.y + src.height / 2;
+
+ var tcx = trg.x + trg.width / 2;
+ var tcy = trg.y + trg.height / 2;
+
+ var dx = tcx - scx;
+ var dy = tcy - scy;
+
+ var len = Math.sqrt(dx*dx+dy*dy);
+
+ var x0 = scx + dx / 2;
+ var y0 = scy + dy / 2;
+
+ var nx = dy * this.spacing / len;
+ var ny = dx * this.spacing / len;
+
+ x0 += nx * (parallels.length - 1) / 2;
+ y0 -= ny * (parallels.length - 1) / 2;
+
+ for (var i = 0; i < parallels.length; i++)
+ {
+ this.route(parallels[i], x0, y0);
+ x0 -= nx;
+ y0 += ny;
+ }
+ }
+};
+
+/**
+ * Function: route
+ *
+ * Routes the given edge via the given point.
+ */
+mxParallelEdgeLayout.prototype.route = function(edge, x, y)
+{
+ if (this.graph.isCellMovable(edge))
+ {
+ this.setEdgePoints(edge, [new mxPoint(x, y)]);
+ }
+};
diff --git a/src/js/layout/mxPartitionLayout.js b/src/js/layout/mxPartitionLayout.js
new file mode 100644
index 0000000..d3592f8
--- /dev/null
+++ b/src/js/layout/mxPartitionLayout.js
@@ -0,0 +1,240 @@
+/**
+ * $Id: mxPartitionLayout.js,v 1.25 2010-01-04 11:18:25 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPartitionLayout
+ *
+ * Extends <mxGraphLayout> for partitioning the parent cell vertically or
+ * horizontally by filling the complete area with the child cells. A horizontal
+ * layout partitions the height of the given parent whereas a a non-horizontal
+ * layout partitions the width. If the parent is a layer (that is, a child of
+ * the root node), then the current graph size is partitioned. The children do
+ * not need to be connected for this layout to work.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxPartitionLayout(graph, true, 10, 20);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxPartitionLayout
+ *
+ * Constructs a new stack layout layout for the specified graph,
+ * spacing, orientation and offset.
+ */
+function mxPartitionLayout(graph, horizontal, spacing, border)
+{
+ mxGraphLayout.call(this, graph);
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.spacing = spacing || 0;
+ this.border = border || 0;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxPartitionLayout.prototype = new mxGraphLayout();
+mxPartitionLayout.prototype.constructor = mxPartitionLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Boolean indicating the direction in which the space is partitioned.
+ * Default is true.
+ */
+mxPartitionLayout.prototype.horizontal = null;
+
+/**
+ * Variable: spacing
+ *
+ * Integer that specifies the absolute spacing in pixels between the
+ * children. Default is 0.
+ */
+mxPartitionLayout.prototype.spacing = null;
+
+/**
+ * Variable: border
+ *
+ * Integer that specifies the absolute inset in pixels for the parent that
+ * contains the children. Default is 0.
+ */
+mxPartitionLayout.prototype.border = null;
+
+/**
+ * Variable: resizeVertices
+ *
+ * Boolean that specifies if vertices should be resized. Default is true.
+ */
+mxPartitionLayout.prototype.resizeVertices = true;
+
+/**
+ * Function: isHorizontal
+ *
+ * Returns <horizontal>.
+ */
+mxPartitionLayout.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ *
+ * Implements <mxGraphLayout.moveCell>.
+ */
+mxPartitionLayout.prototype.moveCell = function(cell, x, y)
+{
+ var model = this.graph.getModel();
+ var parent = model.getParent(cell);
+
+ if (cell != null &&
+ parent != null)
+ {
+ var i = 0;
+ var last = 0;
+ var childCount = model.getChildCount(parent);
+
+ // Finds index of the closest swimlane
+ // TODO: Take into account the orientation
+ for (i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+ var bounds = this.getVertexBounds(child);
+
+ if (bounds != null)
+ {
+ var tmp = bounds.x + bounds.width / 2;
+
+ if (last < x && tmp > x)
+ {
+ break;
+ }
+
+ last = tmp;
+ }
+ }
+
+ // Changes child order in parent
+ var idx = parent.getIndex(cell);
+ idx = Math.max(0, i - ((i > idx) ? 1 : 0));
+
+ model.add(parent, cell, idx);
+ }
+};
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>. All children where <isVertexIgnored>
+ * returns false and <isVertexMovable> returns true are modified.
+ */
+mxPartitionLayout.prototype.execute = function(parent)
+{
+ var horizontal = this.isHorizontal();
+ var model = this.graph.getModel();
+ var pgeo = model.getGeometry(parent);
+
+ // Handles special case where the parent is either a layer with no
+ // geometry or the current root of the view in which case the size
+ // of the graph's container will be used.
+ if (this.graph.container != null &&
+ ((pgeo == null &&
+ model.isLayer(parent)) ||
+ parent == this.graph.getView().currentRoot))
+ {
+ var width = this.graph.container.offsetWidth - 1;
+ var height = this.graph.container.offsetHeight - 1;
+ pgeo = new mxRectangle(0, 0, width, height);
+ }
+
+ if (pgeo != null)
+ {
+ var children = [];
+ var childCount = model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (!this.isVertexIgnored(child) &&
+ this.isVertexMovable(child))
+ {
+ children.push(child);
+ }
+ }
+
+ var n = children.length;
+
+ if (n > 0)
+ {
+ var x0 = this.border;
+ var y0 = this.border;
+ var other = (horizontal) ? pgeo.height : pgeo.width;
+ other -= 2 * this.border;
+
+ var size = (this.graph.isSwimlane(parent)) ?
+ this.graph.getStartSize(parent) :
+ new mxRectangle();
+
+ other -= (horizontal) ? size.height : size.width;
+ x0 = x0 + size.width;
+ y0 = y0 + size.height;
+
+ var tmp = this.border + (n - 1) * this.spacing;
+ var value = (horizontal) ?
+ ((pgeo.width - x0 - tmp) / n) :
+ ((pgeo.height - y0 - tmp) / n);
+
+ // Avoids negative values, that is values where the sum of the
+ // spacing plus the border is larger then the available space
+ if (value > 0)
+ {
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < n; i++)
+ {
+ var child = children[i];
+ var geo = model.getGeometry(child);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ geo.x = x0;
+ geo.y = y0;
+
+ if (horizontal)
+ {
+ if (this.resizeVertices)
+ {
+ geo.width = value;
+ geo.height = other;
+ }
+
+ x0 += value + this.spacing;
+ }
+ else
+ {
+ if (this.resizeVertices)
+ {
+ geo.height = value;
+ geo.width = other;
+ }
+
+ y0 += value + this.spacing;
+ }
+
+ model.setGeometry(child, geo);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ }
+ }
+};
diff --git a/src/js/layout/mxStackLayout.js b/src/js/layout/mxStackLayout.js
new file mode 100644
index 0000000..7f5cd47
--- /dev/null
+++ b/src/js/layout/mxStackLayout.js
@@ -0,0 +1,381 @@
+/**
+ * $Id: mxStackLayout.js,v 1.47 2012-12-14 08:54:34 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxStackLayout
+ *
+ * Extends <mxGraphLayout> to create a horizontal or vertical stack of the
+ * child vertices. The children do not need to be connected for this layout
+ * to work.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxStackLayout(graph, true);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxStackLayout
+ *
+ * Constructs a new stack layout layout for the specified graph,
+ * spacing, orientation and offset.
+ */
+function mxStackLayout(graph, horizontal, spacing, x0, y0, border)
+{
+ mxGraphLayout.call(this, graph);
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.spacing = (spacing != null) ? spacing : 0;
+ this.x0 = (x0 != null) ? x0 : 0;
+ this.y0 = (y0 != null) ? y0 : 0;
+ this.border = (border != null) ? border : 0;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxStackLayout.prototype = new mxGraphLayout();
+mxStackLayout.prototype.constructor = mxStackLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the layout. Default is true.
+ */
+mxStackLayout.prototype.horizontal = null;
+
+/**
+ * Variable: spacing
+ *
+ * Specifies the spacing between the cells. Default is 0.
+ */
+mxStackLayout.prototype.spacing = null;
+
+/**
+ * Variable: x0
+ *
+ * Specifies the horizontal origin of the layout. Default is 0.
+ */
+mxStackLayout.prototype.x0 = null;
+
+/**
+ * Variable: y0
+ *
+ * Specifies the vertical origin of the layout. Default is 0.
+ */
+mxStackLayout.prototype.y0 = null;
+
+/**
+ * Variable: border
+ *
+ * Border to be added if fill is true. Default is 0.
+ */
+mxStackLayout.prototype.border = 0;
+
+/**
+ * Variable: keepFirstLocation
+ *
+ * Boolean indicating if the location of the first cell should be
+ * kept, that is, it will not be moved to x0 or y0.
+ */
+mxStackLayout.prototype.keepFirstLocation = false;
+
+/**
+ * Variable: fill
+ *
+ * Boolean indicating if dimension should be changed to fill out the parent
+ * cell. Default is false.
+ */
+mxStackLayout.prototype.fill = false;
+
+/**
+ * Variable: resizeParent
+ *
+ * If the parent should be resized to match the width/height of the
+ * stack. Default is false.
+ */
+mxStackLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: resizeLast
+ *
+ * If the last element should be resized to fill out the parent. Default is
+ * false. If <resizeParent> is true then this is ignored.
+ */
+mxStackLayout.prototype.resizeLast = false;
+
+/**
+ * Variable: wrap
+ *
+ * Value at which a new column or row should be created. Default is null.
+ */
+mxStackLayout.prototype.wrap = null;
+
+/**
+ * Function: isHorizontal
+ *
+ * Returns <horizontal>.
+ */
+mxStackLayout.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ *
+ * Implements <mxGraphLayout.moveCell>.
+ */
+mxStackLayout.prototype.moveCell = function(cell, x, y)
+{
+ var model = this.graph.getModel();
+ var parent = model.getParent(cell);
+ var horizontal = this.isHorizontal();
+
+ if (cell != null && parent != null)
+ {
+ var i = 0;
+ var last = 0;
+ var childCount = model.getChildCount(parent);
+ var value = (horizontal) ? x : y;
+ var pstate = this.graph.getView().getState(parent);
+
+ if (pstate != null)
+ {
+ value -= (horizontal) ? pstate.x : pstate.y;
+ }
+
+ for (i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (child != cell)
+ {
+ var bounds = model.getGeometry(child);
+
+ if (bounds != null)
+ {
+ var tmp = (horizontal) ?
+ bounds.x + bounds.width / 2 :
+ bounds.y + bounds.height / 2;
+
+ if (last < value && tmp > value)
+ {
+ break;
+ }
+
+ last = tmp;
+ }
+ }
+ }
+
+ // Changes child order in parent
+ var idx = parent.getIndex(cell);
+ idx = Math.max(0, i - ((i > idx) ? 1 : 0));
+
+ model.add(parent, cell, idx);
+ }
+};
+
+/**
+ * Function: getParentSize
+ *
+ * Returns the size for the parent container or the size of the graph
+ * container if the parent is a layer or the root of the model.
+ */
+mxStackLayout.prototype.getParentSize = function(parent)
+{
+ var model = this.graph.getModel();
+ var pgeo = model.getGeometry(parent);
+
+ // Handles special case where the parent is either a layer with no
+ // geometry or the current root of the view in which case the size
+ // of the graph's container will be used.
+ if (this.graph.container != null && ((pgeo == null &&
+ model.isLayer(parent)) || parent == this.graph.getView().currentRoot))
+ {
+ var width = this.graph.container.offsetWidth - 1;
+ var height = this.graph.container.offsetHeight - 1;
+ pgeo = new mxRectangle(0, 0, width, height);
+ }
+
+ return pgeo;
+};
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>.
+ *
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.execute = function(parent)
+{
+ if (parent != null)
+ {
+ var horizontal = this.isHorizontal();
+ var model = this.graph.getModel();
+ var pgeo = this.getParentSize(parent);
+
+ var fillValue = 0;
+
+ if (pgeo != null)
+ {
+ fillValue = (horizontal) ? pgeo.height : pgeo.width;
+ }
+
+ fillValue -= 2 * this.spacing + 2 * this.border;
+ var x0 = this.x0 + this.border;
+ var y0 = this.y0 + this.border;
+
+ // Handles swimlane start size
+ if (this.graph.isSwimlane(parent))
+ {
+ // Uses computed style to get latest
+ var style = this.graph.getCellStyle(parent);
+ var start = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE);
+ var horz = mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true);
+
+ if (horizontal == horz)
+ {
+ fillValue -= start;
+ }
+
+ if (horizontal)
+ {
+ y0 += start;
+ }
+ else
+ {
+ x0 += start;
+ }
+ }
+
+ model.beginUpdate();
+ try
+ {
+ var tmp = 0;
+ var last = null;
+ var childCount = model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (!this.isVertexIgnored(child) && this.isVertexMovable(child))
+ {
+ var geo = model.getGeometry(child);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+
+ if (this.wrap != null && last != null)
+ {
+ if ((horizontal && last.x + last.width +
+ geo.width + 2 * this.spacing > this.wrap) ||
+ (!horizontal && last.y + last.height +
+ geo.height + 2 * this.spacing > this.wrap))
+ {
+ last = null;
+
+ if (horizontal)
+ {
+ y0 += tmp + this.spacing;
+ }
+ else
+ {
+ x0 += tmp + this.spacing;
+ }
+
+ tmp = 0;
+ }
+ }
+
+ tmp = Math.max(tmp, (horizontal) ? geo.height : geo.width);
+
+ if (last != null)
+ {
+ if (horizontal)
+ {
+ geo.x = last.x + last.width + this.spacing;
+ }
+ else
+ {
+ geo.y = last.y + last.height + this.spacing;
+ }
+ }
+ else if (!this.keepFirstLocation)
+ {
+ if (horizontal)
+ {
+ geo.x = x0;
+ }
+ else
+ {
+ geo.y = y0;
+ }
+ }
+
+ if (horizontal)
+ {
+ geo.y = y0;
+ }
+ else
+ {
+ geo.x = x0;
+ }
+
+ if (this.fill && fillValue > 0)
+ {
+ if (horizontal)
+ {
+ geo.height = fillValue;
+ }
+ else
+ {
+ geo.width = fillValue;
+ }
+ }
+
+ model.setGeometry(child, geo);
+ last = geo;
+ }
+ }
+ }
+
+ if (this.resizeParent && pgeo != null && last != null &&
+ !this.graph.isCellCollapsed(parent))
+ {
+ pgeo = pgeo.clone();
+
+ if (horizontal)
+ {
+ pgeo.width = last.x + last.width + this.spacing;
+ }
+ else
+ {
+ pgeo.height = last.y + last.height + this.spacing;
+ }
+
+ model.setGeometry(parent, pgeo);
+ }
+ else if (this.resizeLast && pgeo != null && last != null)
+ {
+ if (horizontal)
+ {
+ last.width = pgeo.width - last.x - this.spacing;
+ }
+ else
+ {
+ last.height = pgeo.height - last.y - this.spacing;
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
diff --git a/src/js/model/mxCell.js b/src/js/model/mxCell.js
new file mode 100644
index 0000000..cb5eb9f
--- /dev/null
+++ b/src/js/model/mxCell.js
@@ -0,0 +1,806 @@
+/**
+ * $Id: mxCell.js,v 1.36 2011-06-17 13:45:08 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCell
+ *
+ * Cells are the elements of the graph model. They represent the state
+ * of the groups, vertices and edges in a graph.
+ *
+ * Custom attributes:
+ *
+ * For custom attributes we recommend using an XML node as the value of a cell.
+ * The following code can be used to create a cell with an XML node as the
+ * value:
+ *
+ * (code)
+ * var doc = mxUtils.createXmlDocument();
+ * var node = doc.createElement('MyNode')
+ * node.setAttribute('label', 'MyLabel');
+ * node.setAttribute('attribute1', 'value1');
+ * graph.insertVertex(graph.getDefaultParent(), null, node, 40, 40, 80, 30);
+ * (end)
+ *
+ * For the label to work, <mxGraph.convertValueToString> and
+ * <mxGraph.cellLabelChanged> should be overridden as follows:
+ *
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ * if (mxUtils.isNode(cell.value))
+ * {
+ * return cell.getAttribute('label', '')
+ * }
+ * };
+ *
+ * var cellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ * if (mxUtils.isNode(cell.value))
+ * {
+ * // Clones the value for correct undo/redo
+ * var elt = cell.value.cloneNode(true);
+ * elt.setAttribute('label', newValue);
+ * newValue = elt;
+ * }
+ *
+ * cellLabelChanged.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Callback: onInit
+ *
+ * Called from within the constructor.
+ *
+ * Constructor: mxCell
+ *
+ * Constructs a new cell to be used in a graph model.
+ * This method invokes <onInit> upon completion.
+ *
+ * Parameters:
+ *
+ * value - Optional object that represents the cell value.
+ * geometry - Optional <mxGeometry> that specifies the geometry.
+ * style - Optional formatted string that defines the style.
+ */
+function mxCell(value, geometry, style)
+{
+ this.value = value;
+ this.setGeometry(geometry);
+ this.setStyle(style);
+
+ if (this.onInit != null)
+ {
+ this.onInit();
+ }
+};
+
+/**
+ * Variable: id
+ *
+ * Holds the Id. Default is null.
+ */
+mxCell.prototype.id = null;
+
+/**
+ * Variable: value
+ *
+ * Holds the user object. Default is null.
+ */
+mxCell.prototype.value = null;
+
+/**
+ * Variable: geometry
+ *
+ * Holds the <mxGeometry>. Default is null.
+ */
+mxCell.prototype.geometry = null;
+
+/**
+ * Variable: style
+ *
+ * Holds the style as a string of the form [(stylename|key=value);]. Default is
+ * null.
+ */
+mxCell.prototype.style = null;
+
+/**
+ * Variable: vertex
+ *
+ * Specifies whether the cell is a vertex. Default is false.
+ */
+mxCell.prototype.vertex = false;
+
+/**
+ * Variable: edge
+ *
+ * Specifies whether the cell is an edge. Default is false.
+ */
+mxCell.prototype.edge = false;
+
+/**
+ * Variable: connectable
+ *
+ * Specifies whether the cell is connectable. Default is true.
+ */
+mxCell.prototype.connectable = true;
+
+/**
+ * Variable: visible
+ *
+ * Specifies whether the cell is visible. Default is true.
+ */
+mxCell.prototype.visible = true;
+
+/**
+ * Variable: collapsed
+ *
+ * Specifies whether the cell is collapsed. Default is false.
+ */
+mxCell.prototype.collapsed = false;
+
+/**
+ * Variable: parent
+ *
+ * Reference to the parent cell.
+ */
+mxCell.prototype.parent = null;
+
+/**
+ * Variable: source
+ *
+ * Reference to the source terminal.
+ */
+mxCell.prototype.source = null;
+
+/**
+ * Variable: target
+ *
+ * Reference to the target terminal.
+ */
+mxCell.prototype.target = null;
+
+/**
+ * Variable: children
+ *
+ * Holds the child cells.
+ */
+mxCell.prototype.children = null;
+
+/**
+ * Variable: edges
+ *
+ * Holds the edges.
+ */
+mxCell.prototype.edges = null;
+
+/**
+ * Variable: mxTransient
+ *
+ * List of members that should not be cloned inside <clone>. This field is
+ * passed to <mxUtils.clone> and is not made persistent in <mxCellCodec>.
+ * This is not a convention for all classes, it is only used in this class
+ * to mark transient fields since transient modifiers are not supported by
+ * the language.
+ */
+mxCell.prototype.mxTransient = ['id', 'value', 'parent', 'source',
+ 'target', 'children', 'edges'];
+
+/**
+ * Function: getId
+ *
+ * Returns the Id of the cell as a string.
+ */
+mxCell.prototype.getId = function()
+{
+ return this.id;
+};
+
+/**
+ * Function: setId
+ *
+ * Sets the Id of the cell to the given string.
+ */
+mxCell.prototype.setId = function(id)
+{
+ this.id = id;
+};
+
+/**
+ * Function: getValue
+ *
+ * Returns the user object of the cell. The user
+ * object is stored in <value>.
+ */
+mxCell.prototype.getValue = function()
+{
+ return this.value;
+};
+
+/**
+ * Function: setValue
+ *
+ * Sets the user object of the cell. The user object
+ * is stored in <value>.
+ */
+mxCell.prototype.setValue = function(value)
+{
+ this.value = value;
+};
+
+/**
+ * Function: valueChanged
+ *
+ * Changes the user object after an in-place edit
+ * and returns the previous value. This implementation
+ * replaces the user object with the given value and
+ * returns the old user object.
+ */
+mxCell.prototype.valueChanged = function(newValue)
+{
+ var previous = this.getValue();
+ this.setValue(newValue);
+
+ return previous;
+};
+
+/**
+ * Function: getGeometry
+ *
+ * Returns the <mxGeometry> that describes the <geometry>.
+ */
+mxCell.prototype.getGeometry = function()
+{
+ return this.geometry;
+};
+
+/**
+ * Function: setGeometry
+ *
+ * Sets the <mxGeometry> to be used as the <geometry>.
+ */
+mxCell.prototype.setGeometry = function(geometry)
+{
+ this.geometry = geometry;
+};
+
+/**
+ * Function: getStyle
+ *
+ * Returns a string that describes the <style>.
+ */
+mxCell.prototype.getStyle = function()
+{
+ return this.style;
+};
+
+/**
+ * Function: setStyle
+ *
+ * Sets the string to be used as the <style>.
+ */
+mxCell.prototype.setStyle = function(style)
+{
+ this.style = style;
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns true if the cell is a vertex.
+ */
+mxCell.prototype.isVertex = function()
+{
+ return this.vertex;
+};
+
+/**
+ * Function: setVertex
+ *
+ * Specifies if the cell is a vertex. This should only be assigned at
+ * construction of the cell and not be changed during its lifecycle.
+ *
+ * Parameters:
+ *
+ * vertex - Boolean that specifies if the cell is a vertex.
+ */
+mxCell.prototype.setVertex = function(vertex)
+{
+ this.vertex = vertex;
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns true if the cell is an edge.
+ */
+mxCell.prototype.isEdge = function()
+{
+ return this.edge;
+};
+
+/**
+ * Function: setEdge
+ *
+ * Specifies if the cell is an edge. This should only be assigned at
+ * construction of the cell and not be changed during its lifecycle.
+ *
+ * Parameters:
+ *
+ * edge - Boolean that specifies if the cell is an edge.
+ */
+mxCell.prototype.setEdge = function(edge)
+{
+ this.edge = edge;
+};
+
+/**
+ * Function: isConnectable
+ *
+ * Returns true if the cell is connectable.
+ */
+mxCell.prototype.isConnectable = function()
+{
+ return this.connectable;
+};
+
+/**
+ * Function: setConnectable
+ *
+ * Sets the connectable state.
+ *
+ * Parameters:
+ *
+ * connectable - Boolean that specifies the new connectable state.
+ */
+mxCell.prototype.setConnectable = function(connectable)
+{
+ this.connectable = connectable;
+};
+
+/**
+ * Function: isVisible
+ *
+ * Returns true if the cell is visibile.
+ */
+mxCell.prototype.isVisible = function()
+{
+ return this.visible;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Specifies if the cell is visible.
+ *
+ * Parameters:
+ *
+ * visible - Boolean that specifies the new visible state.
+ */
+mxCell.prototype.setVisible = function(visible)
+{
+ this.visible = visible;
+};
+
+/**
+ * Function: isCollapsed
+ *
+ * Returns true if the cell is collapsed.
+ */
+mxCell.prototype.isCollapsed = function()
+{
+ return this.collapsed;
+};
+
+/**
+ * Function: setCollapsed
+ *
+ * Sets the collapsed state.
+ *
+ * Parameters:
+ *
+ * collapsed - Boolean that specifies the new collapsed state.
+ */
+mxCell.prototype.setCollapsed = function(collapsed)
+{
+ this.collapsed = collapsed;
+};
+
+/**
+ * Function: getParent
+ *
+ * Returns the cell's parent.
+ */
+mxCell.prototype.getParent = function()
+{
+ return this.parent;
+};
+
+/**
+ * Function: setParent
+ *
+ * Sets the parent cell.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that represents the new parent.
+ */
+mxCell.prototype.setParent = function(parent)
+{
+ this.parent = parent;
+};
+
+/**
+ * Function: getTerminal
+ *
+ * Returns the source or target terminal.
+ *
+ * Parameters:
+ *
+ * source - Boolean that specifies if the source terminal should be
+ * returned.
+ */
+mxCell.prototype.getTerminal = function(source)
+{
+ return (source) ? this.source : this.target;
+};
+
+/**
+ * Function: setTerminal
+ *
+ * Sets the source or target terminal and returns the new terminal.
+ *
+ * Parameters:
+ *
+ * terminal - <mxCell> that represents the new source or target terminal.
+ * isSource - Boolean that specifies if the source or target terminal
+ * should be set.
+ */
+mxCell.prototype.setTerminal = function(terminal, isSource)
+{
+ if (isSource)
+ {
+ this.source = terminal;
+ }
+ else
+ {
+ this.target = terminal;
+ }
+
+ return terminal;
+};
+
+/**
+ * Function: getChildCount
+ *
+ * Returns the number of child cells.
+ */
+mxCell.prototype.getChildCount = function()
+{
+ return (this.children == null) ? 0 : this.children.length;
+};
+
+/**
+ * Function: getIndex
+ *
+ * Returns the index of the specified child in the child array.
+ *
+ * Parameters:
+ *
+ * child - Child whose index should be returned.
+ */
+mxCell.prototype.getIndex = function(child)
+{
+ return mxUtils.indexOf(this.children, child);
+};
+
+/**
+ * Function: getChildAt
+ *
+ * Returns the child at the specified index.
+ *
+ * Parameters:
+ *
+ * index - Integer that specifies the child to be returned.
+ */
+mxCell.prototype.getChildAt = function(index)
+{
+ return (this.children == null) ? null : this.children[index];
+};
+
+/**
+ * Function: insert
+ *
+ * Inserts the specified child into the child array at the specified index
+ * and updates the parent reference of the child. If not childIndex is
+ * specified then the child is appended to the child array. Returns the
+ * inserted child.
+ *
+ * Parameters:
+ *
+ * child - <mxCell> to be inserted or appended to the child array.
+ * index - Optional integer that specifies the index at which the child
+ * should be inserted into the child array.
+ */
+mxCell.prototype.insert = function(child, index)
+{
+ if (child != null)
+ {
+ if (index == null)
+ {
+ index = this.getChildCount();
+
+ if (child.getParent() == this)
+ {
+ index--;
+ }
+ }
+
+ child.removeFromParent();
+ child.setParent(this);
+
+ if (this.children == null)
+ {
+ this.children = [];
+ this.children.push(child);
+ }
+ else
+ {
+ this.children.splice(index, 0, child);
+ }
+ }
+
+ return child;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the child at the specified index from the child array and
+ * returns the child that was removed. Will remove the parent reference of
+ * the child.
+ *
+ * Parameters:
+ *
+ * index - Integer that specifies the index of the child to be
+ * removed.
+ */
+mxCell.prototype.remove = function(index)
+{
+ var child = null;
+
+ if (this.children != null && index >= 0)
+ {
+ child = this.getChildAt(index);
+
+ if (child != null)
+ {
+ this.children.splice(index, 1);
+ child.setParent(null);
+ }
+ }
+
+ return child;
+};
+
+/**
+ * Function: removeFromParent
+ *
+ * Removes the cell from its parent.
+ */
+mxCell.prototype.removeFromParent = function()
+{
+ if (this.parent != null)
+ {
+ var index = this.parent.getIndex(this);
+ this.parent.remove(index);
+ }
+};
+
+/**
+ * Function: getEdgeCount
+ *
+ * Returns the number of edges in the edge array.
+ */
+mxCell.prototype.getEdgeCount = function()
+{
+ return (this.edges == null) ? 0 : this.edges.length;
+};
+
+/**
+ * Function: getEdgeIndex
+ *
+ * Returns the index of the specified edge in <edges>.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose index in <edges> should be returned.
+ */
+mxCell.prototype.getEdgeIndex = function(edge)
+{
+ return mxUtils.indexOf(this.edges, edge);
+};
+
+/**
+ * Function: getEdgeAt
+ *
+ * Returns the edge at the specified index in <edges>.
+ *
+ * Parameters:
+ *
+ * index - Integer that specifies the index of the edge to be returned.
+ */
+mxCell.prototype.getEdgeAt = function(index)
+{
+ return (this.edges == null) ? null : this.edges[index];
+};
+
+/**
+ * Function: insertEdge
+ *
+ * Inserts the specified edge into the edge array and returns the edge.
+ * Will update the respective terminal reference of the edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> to be inserted into the edge array.
+ * isOutgoing - Boolean that specifies if the edge is outgoing.
+ */
+mxCell.prototype.insertEdge = function(edge, isOutgoing)
+{
+ if (edge != null)
+ {
+ edge.removeFromTerminal(isOutgoing);
+ edge.setTerminal(this, isOutgoing);
+
+ if (this.edges == null ||
+ edge.getTerminal(!isOutgoing) != this ||
+ mxUtils.indexOf(this.edges, edge) < 0)
+ {
+ if (this.edges == null)
+ {
+ this.edges = [];
+ }
+
+ this.edges.push(edge);
+ }
+ }
+
+ return edge;
+};
+
+/**
+ * Function: removeEdge
+ *
+ * Removes the specified edge from the edge array and returns the edge.
+ * Will remove the respective terminal reference from the edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> to be removed from the edge array.
+ * isOutgoing - Boolean that specifies if the edge is outgoing.
+ */
+mxCell.prototype.removeEdge = function(edge, isOutgoing)
+{
+ if (edge != null)
+ {
+ if (edge.getTerminal(!isOutgoing) != this &&
+ this.edges != null)
+ {
+ var index = this.getEdgeIndex(edge);
+
+ if (index >= 0)
+ {
+ this.edges.splice(index, 1);
+ }
+ }
+
+ edge.setTerminal(null, isOutgoing);
+ }
+
+ return edge;
+};
+
+/**
+ * Function: removeFromTerminal
+ *
+ * Removes the edge from its source or target terminal.
+ *
+ * Parameters:
+ *
+ * isSource - Boolean that specifies if the edge should be removed from its
+ * source or target terminal.
+ */
+mxCell.prototype.removeFromTerminal = function(isSource)
+{
+ var terminal = this.getTerminal(isSource);
+
+ if (terminal != null)
+ {
+ terminal.removeEdge(this, isSource);
+ }
+};
+
+/**
+ * Function: getAttribute
+ *
+ * Returns the specified attribute from the user object if it is an XML
+ * node.
+ *
+ * Parameters:
+ *
+ * name - Name of the attribute whose value should be returned.
+ * defaultValue - Optional default value to use if the attribute has no
+ * value.
+ */
+mxCell.prototype.getAttribute = function(name, defaultValue)
+{
+ var userObject = this.getValue();
+
+ var val = (userObject != null &&
+ userObject.nodeType == mxConstants.NODETYPE_ELEMENT) ?
+ userObject.getAttribute(name) : null;
+
+ return val || defaultValue;
+};
+
+/**
+ * Function: setAttribute
+ *
+ * Sets the specified attribute on the user object if it is an XML node.
+ *
+ * Parameters:
+ *
+ * name - Name of the attribute whose value should be set.
+ * value - New value of the attribute.
+ */
+mxCell.prototype.setAttribute = function(name, value)
+{
+ var userObject = this.getValue();
+
+ if (userObject != null &&
+ userObject.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ userObject.setAttribute(name, value);
+ }
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of the cell. Uses <cloneValue> to clone
+ * the user object. All fields in <mxTransient> are ignored
+ * during the cloning.
+ */
+mxCell.prototype.clone = function()
+{
+ var clone = mxUtils.clone(this, this.mxTransient);
+ clone.setValue(this.cloneValue());
+
+ return clone;
+};
+
+/**
+ * Function: cloneValue
+ *
+ * Returns a clone of the cell's user object.
+ */
+mxCell.prototype.cloneValue = function()
+{
+ var value = this.getValue();
+
+ if (value != null)
+ {
+ if (typeof(value.clone) == 'function')
+ {
+ value = value.clone();
+ }
+ else if (!isNaN(value.nodeType))
+ {
+ value = value.cloneNode(true);
+ }
+ }
+
+ return value;
+};
diff --git a/src/js/model/mxCellPath.js b/src/js/model/mxCellPath.js
new file mode 100644
index 0000000..71a379e
--- /dev/null
+++ b/src/js/model/mxCellPath.js
@@ -0,0 +1,163 @@
+/**
+ * $Id: mxCellPath.js,v 1.12 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxCellPath =
+{
+
+ /**
+ * Class: mxCellPath
+ *
+ * Implements a mechanism for temporary cell Ids.
+ *
+ * Variable: PATH_SEPARATOR
+ *
+ * Defines the separator between the path components. Default is ".".
+ */
+ PATH_SEPARATOR: '.',
+
+ /**
+ * Function: create
+ *
+ * Creates the cell path for the given cell. The cell path is a
+ * concatenation of the indices of all ancestors on the (finite) path to
+ * the root, eg. "0.0.0.1".
+ *
+ * Parameters:
+ *
+ * cell - Cell whose path should be returned.
+ */
+ create: function(cell)
+ {
+ var result = '';
+
+ if (cell != null)
+ {
+ var parent = cell.getParent();
+
+ while (parent != null)
+ {
+ var index = parent.getIndex(cell);
+ result = index + mxCellPath.PATH_SEPARATOR + result;
+
+ cell = parent;
+ parent = cell.getParent();
+ }
+ }
+
+ // Removes trailing separator
+ var n = result.length;
+
+ if (n > 1)
+ {
+ result = result.substring(0, n - 1);
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: getParentPath
+ *
+ * Returns the path for the parent of the cell represented by the given
+ * path. Returns null if the given path has no parent.
+ *
+ * Parameters:
+ *
+ * path - Path whose parent path should be returned.
+ */
+ getParentPath: function(path)
+ {
+ if (path != null)
+ {
+ var index = path.lastIndexOf(mxCellPath.PATH_SEPARATOR);
+
+ if (index >= 0)
+ {
+ return path.substring(0, index);
+ }
+ else if (path.length > 0)
+ {
+ return '';
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Function: resolve
+ *
+ * Returns the cell for the specified cell path using the given root as the
+ * root of the path.
+ *
+ * Parameters:
+ *
+ * root - Root cell of the path to be resolved.
+ * path - String that defines the path.
+ */
+ resolve: function(root, path)
+ {
+ var parent = root;
+
+ if (path != null)
+ {
+ var tokens = path.split(mxCellPath.PATH_SEPARATOR);
+
+ for (var i=0; i<tokens.length; i++)
+ {
+ parent = parent.getChildAt(parseInt(tokens[i]));
+ }
+ }
+
+ return parent;
+ },
+
+ /**
+ * Function: compare
+ *
+ * Compares the given cell paths and returns -1 if p1 is smaller, 0 if
+ * p1 is equal and 1 if p1 is greater than p2.
+ */
+ compare: function(p1, p2)
+ {
+ var min = Math.min(p1.length, p2.length);
+ var comp = 0;
+
+ for (var i = 0; i < min; i++)
+ {
+ if (p1[i] != p2[i])
+ {
+ if (p1[i].length == 0 ||
+ p2[i].length == 0)
+ {
+ comp = (p1[i] == p2[i]) ? 0 : ((p1[i] > p2[i]) ? 1 : -1);
+ }
+ else
+ {
+ var t1 = parseInt(p1[i]);
+ var t2 = parseInt(p2[i]);
+
+ comp = (t1 == t2) ? 0 : ((t1 > t2) ? 1 : -1);
+ }
+
+ break;
+ }
+ }
+
+ // Compares path length if both paths are equal to this point
+ if (comp == 0)
+ {
+ var t1 = p1.length;
+ var t2 = p2.length;
+
+ if (t1 != t2)
+ {
+ comp = (t1 > t2) ? 1 : -1;
+ }
+ }
+
+ return comp;
+ }
+
+};
diff --git a/src/js/model/mxGeometry.js b/src/js/model/mxGeometry.js
new file mode 100644
index 0000000..51a7d3b
--- /dev/null
+++ b/src/js/model/mxGeometry.js
@@ -0,0 +1,277 @@
+/**
+ * $Id: mxGeometry.js,v 1.26 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGeometry
+ *
+ * Extends <mxRectangle> to represent the geometry of a cell.
+ *
+ * For vertices, the geometry consists of the x- and y-location, and the width
+ * and height. For edges, the geometry consists of the optional terminal- and
+ * control points. The terminal points are only required if an edge is
+ * unconnected, and are stored in the sourcePoint> and <targetPoint>
+ * variables, respectively.
+ *
+ * Example:
+ *
+ * If an edge is unconnected, that is, it has no source or target terminal,
+ * then a geometry with terminal points for a new edge can be defined as
+ * follows.
+ *
+ * (code)
+ * geometry.setTerminalPoint(new mxPoint(x1, y1), true);
+ * geometry.points = [new mxPoint(x2, y2)];
+ * geometry.setTerminalPoint(new mxPoint(x3, y3), false);
+ * (end)
+ *
+ * Control points are used regardless of the connected state of an edge and may
+ * be ignored or interpreted differently depending on the edge's <mxEdgeStyle>.
+ *
+ * To disable automatic reset of control points after a cell has been moved or
+ * resized, the the <mxGraph.resizeEdgesOnMove> and
+ * <mxGraph.resetEdgesOnResize> may be used.
+ *
+ * Edge Labels:
+ *
+ * Using the x- and y-coordinates of a cell's geometry, it is possible to
+ * position the label on edges on a specific location on the actual edge shape
+ * as it appears on the screen. The x-coordinate of an edge's geometry is used
+ * to describe the distance from the center of the edge from -1 to 1 with 0
+ * being the center of the edge and the default value. The y-coordinate of an
+ * edge's geometry is used to describe the absolute, orthogonal distance in
+ * pixels from that point. In addition, the <mxGeometry.offset> is used as an
+ * absolute offset vector from the resulting point.
+ *
+ * This coordinate system is applied if <relative> is true, otherwise the
+ * offset defines the absolute vector from the edge's center point to the
+ * label.
+ *
+ * Ports:
+ *
+ * The term "port" refers to a relatively positioned, connectable child cell,
+ * which is used to specify the connection between the parent and another cell
+ * in the graph. Ports are typically modeled as vertices with relative
+ * geometries.
+ *
+ * Offsets:
+ *
+ * The <offset> field is interpreted in 3 different ways, depending on the cell
+ * and the geometry. For edges, the offset defines the absolute offset for the
+ * edge label. For relative geometries, the offset defines the absolute offset
+ * for the origin (top, left corner) of the vertex, otherwise the offset
+ * defines the absolute offset for the label inside the vertex or group.
+ *
+ * Constructor: mxGeometry
+ *
+ * Constructs a new object to describe the size and location of a vertex or
+ * the control points of an edge.
+ */
+function mxGeometry(x, y, width, height)
+{
+ mxRectangle.call(this, x, y, width, height);
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxGeometry.prototype = new mxRectangle();
+mxGeometry.prototype.constructor = mxGeometry;
+
+/**
+ * Variable: TRANSLATE_CONTROL_POINTS
+ *
+ * Global switch to translate the points in translate. Default is true.
+ */
+mxGeometry.prototype.TRANSLATE_CONTROL_POINTS = true;
+
+/**
+ * Variable: alternateBounds
+ *
+ * Stores alternate values for x, y, width and height in a rectangle. See
+ * <swap> to exchange the values. Default is null.
+ */
+mxGeometry.prototype.alternateBounds = null;
+
+/**
+ * Variable: sourcePoint
+ *
+ * Defines the source <mxPoint> of the edge. This is used if the
+ * corresponding edge does not have a source vertex. Otherwise it is
+ * ignored. Default is null.
+ */
+mxGeometry.prototype.sourcePoint = null;
+
+/**
+ * Variable: targetPoint
+ *
+ * Defines the target <mxPoint> of the edge. This is used if the
+ * corresponding edge does not have a target vertex. Otherwise it is
+ * ignored. Default is null.
+ */
+mxGeometry.prototype.targetPoint = null;
+
+/**
+ * Variable: points
+ *
+ * Array of <mxPoints> which specifies the control points along the edge.
+ * These points are the intermediate points on the edge, for the endpoints
+ * use <targetPoint> and <sourcePoint> or set the terminals of the edge to
+ * a non-null value. Default is null.
+ */
+mxGeometry.prototype.points = null;
+
+/**
+ * Variable: offset
+ *
+ * For edges, this holds the offset (in pixels) from the position defined
+ * by <x> and <y> on the edge. For relative geometries (for vertices), this
+ * defines the absolute offset from the point defined by the relative
+ * coordinates. For absolute geometries (for vertices), this defines the
+ * offset for the label. Default is null.
+ */
+mxGeometry.prototype.offset = null;
+
+/**
+ * Variable: relative
+ *
+ * Specifies if the coordinates in the geometry are to be interpreted as
+ * relative coordinates. For edges, this is used to define the location of
+ * the edge label relative to the edge as rendered on the display. For
+ * vertices, this specifies the relative location inside the bounds of the
+ * parent cell.
+ *
+ * If this is false, then the coordinates are relative to the origin of the
+ * parent cell or, for edges, the edge label position is relative to the
+ * center of the edge as rendered on screen.
+ *
+ * Default is false.
+ */
+mxGeometry.prototype.relative = false;
+
+/**
+ * Function: swap
+ *
+ * Swaps the x, y, width and height with the values stored in
+ * <alternateBounds> and puts the previous values into <alternateBounds> as
+ * a rectangle. This operation is carried-out in-place, that is, using the
+ * existing geometry instance. If this operation is called during a graph
+ * model transactional change, then the geometry should be cloned before
+ * calling this method and setting the geometry of the cell using
+ * <mxGraphModel.setGeometry>.
+ */
+mxGeometry.prototype.swap = function()
+{
+ if (this.alternateBounds != null)
+ {
+ var old = new mxRectangle(
+ this.x, this.y, this.width, this.height);
+
+ this.x = this.alternateBounds.x;
+ this.y = this.alternateBounds.y;
+ this.width = this.alternateBounds.width;
+ this.height = this.alternateBounds.height;
+
+ this.alternateBounds = old;
+ }
+};
+
+/**
+ * Function: getTerminalPoint
+ *
+ * Returns the <mxPoint> representing the source or target point of this
+ * edge. This is only used if the edge has no source or target vertex.
+ *
+ * Parameters:
+ *
+ * isSource - Boolean that specifies if the source or target point
+ * should be returned.
+ */
+mxGeometry.prototype.getTerminalPoint = function(isSource)
+{
+ return (isSource) ? this.sourcePoint : this.targetPoint;
+};
+
+/**
+ * Function: setTerminalPoint
+ *
+ * Sets the <sourcePoint> or <targetPoint> to the given <mxPoint> and
+ * returns the new point.
+ *
+ * Parameters:
+ *
+ * point - Point to be used as the new source or target point.
+ * isSource - Boolean that specifies if the source or target point
+ * should be set.
+ */
+mxGeometry.prototype.setTerminalPoint = function(point, isSource)
+{
+ if (isSource)
+ {
+ this.sourcePoint = point;
+ }
+ else
+ {
+ this.targetPoint = point;
+ }
+
+ return point;
+};
+
+/**
+ * Function: translate
+ *
+ * Translates the geometry by the specified amount. That is, <x> and <y>
+ * of the geometry, the <sourcePoint>, <targetPoint> and all elements of
+ * <points> are translated by the given amount. <x> and <y> are only
+ * translated if <relative> is false. If <TRANSLATE_CONTROL_POINTS> is
+ * false, then <points> are not modified by this function.
+ *
+ * Parameters:
+ *
+ * dx - Integer that specifies the x-coordinate of the translation.
+ * dy - Integer that specifies the y-coordinate of the translation.
+ */
+mxGeometry.prototype.translate = function(dx, dy)
+{
+ var clone = this.clone();
+
+ // Translates the geometry
+ if (!this.relative)
+ {
+ this.x += dx;
+ this.y += dy;
+ }
+
+ // Translates the source point
+ if (this.sourcePoint != null)
+ {
+ this.sourcePoint.x += dx;
+ this.sourcePoint.y += dy;
+ }
+
+ // Translates the target point
+ if (this.targetPoint != null)
+ {
+ this.targetPoint.x += dx;
+ this.targetPoint.y += dy;
+ }
+
+ // Translate the control points
+ if (this.TRANSLATE_CONTROL_POINTS &&
+ this.points != null)
+ {
+ var count = this.points.length;
+
+ for (var i = 0; i < count; i++)
+ {
+ var pt = this.points[i];
+
+ if (pt != null)
+ {
+ pt.x += dx;
+ pt.y += dy;
+ }
+ }
+ }
+};
diff --git a/src/js/model/mxGraphModel.js b/src/js/model/mxGraphModel.js
new file mode 100644
index 0000000..c65c0e1
--- /dev/null
+++ b/src/js/model/mxGraphModel.js
@@ -0,0 +1,2622 @@
+/**
+ * $Id: mxGraphModel.js,v 1.125 2012-04-16 10:48:43 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphModel
+ *
+ * Extends <mxEventSource> to implement a graph model. The graph model acts as
+ * a wrapper around the cells which are in charge of storing the actual graph
+ * datastructure. The model acts as a transactional wrapper with event
+ * notification for all changes, whereas the cells contain the atomic
+ * operations for updating the actual datastructure.
+ *
+ * Layers:
+ *
+ * The cell hierarchy in the model must have a top-level root cell which
+ * contains the layers (typically one default layer), which in turn contain the
+ * top-level cells of the layers. This means each cell is contained in a layer.
+ * If no layers are required, then all new cells should be added to the default
+ * layer.
+ *
+ * Layers are useful for hiding and showing groups of cells, or for placing
+ * groups of cells on top of other cells in the display. To identify a layer,
+ * the <isLayer> function is used. It returns true if the parent of the given
+ * cell is the root of the model.
+ *
+ * Encoding the model:
+ *
+ * To encode a graph model, use the following code:
+ *
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ *
+ * This will create an XML node that contains all the model information.
+ *
+ * Encoding and decoding changes:
+ *
+ * For the encoding of changes, a graph model listener is required that encodes
+ * each change from the given array of changes.
+ *
+ * (code)
+ * model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ * var changes = evt.getProperty('edit').changes;
+ * var nodes = [];
+ * var codec = new mxCodec();
+ *
+ * for (var i = 0; i < changes.length; i++)
+ * {
+ * nodes.push(codec.encode(changes[i]));
+ * }
+ * // do something with the nodes
+ * });
+ * (end)
+ *
+ * For the decoding and execution of changes, the codec needs a lookup function
+ * that allows it to resolve cell IDs as follows:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.lookup = function(id)
+ * {
+ * return model.getCell(id);
+ * }
+ * (end)
+ *
+ * For each encoded change (represented by a node), the following code can be
+ * used to carry out the decoding and create a change object.
+ *
+ * (code)
+ * var changes = [];
+ * var change = codec.decode(node);
+ * change.model = model;
+ * change.execute();
+ * changes.push(change);
+ * (end)
+ *
+ * The changes can then be dispatched using the model as follows.
+ *
+ * (code)
+ * var edit = new mxUndoableEdit(model, false);
+ * edit.changes = changes;
+ *
+ * edit.notify = function()
+ * {
+ * edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ * 'edit', edit, 'changes', edit.changes));
+ * edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ * 'edit', edit, 'changes', edit.changes));
+ * }
+ *
+ * model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ * model.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ * 'edit', edit, 'changes', changes));
+ * (end)
+ *
+ * Event: mxEvent.CHANGE
+ *
+ * Fires when an undoable edit is dispatched. The <code>edit</code> property
+ * contains the <mxUndoableEdit>. The <code>changes</code> property contains
+ * the array of atomic changes inside the undoable edit. The changes property
+ * is <strong>deprecated</strong>, please use edit.changes instead.
+ *
+ * Example:
+ *
+ * For finding newly inserted cells, the following code can be used:
+ *
+ * (code)
+ * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ * var changes = evt.getProperty('edit').changes;
+ *
+ * for (var i = 0; i < changes.length; i++)
+ * {
+ * var change = changes[i];
+ *
+ * if (change instanceof mxChildChange &&
+ * change.change.previous == null)
+ * {
+ * graph.startEditingAtCell(change.child);
+ * break;
+ * }
+ * }
+ * });
+ * (end)
+ *
+ *
+ * Event: mxEvent.NOTIFY
+ *
+ * Same as <mxEvent.CHANGE>, this event can be used for classes that need to
+ * implement a sync mechanism between this model and, say, a remote model. In
+ * such a setup, only local changes should trigger a notify event and all
+ * changes should trigger a change event.
+ *
+ * Event: mxEvent.EXECUTE
+ *
+ * Fires between begin- and endUpdate and after an atomic change was executed
+ * in the model. The <code>change</code> property contains the atomic change
+ * that was executed.
+ *
+ * Event: mxEvent.BEGIN_UPDATE
+ *
+ * Fires after the <updateLevel> was incremented in <beginUpdate>. This event
+ * contains no properties.
+ *
+ * Event: mxEvent.END_UPDATE
+ *
+ * Fires after the <updateLevel> was decreased in <endUpdate> but before any
+ * notification or change dispatching. The <code>edit</code> property contains
+ * the <currentEdit>.
+ *
+ * Event: mxEvent.BEFORE_UNDO
+ *
+ * Fires before the change is dispatched after the update level has reached 0
+ * in <endUpdate>. The <code>edit</code> property contains the <curreneEdit>.
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires after the change was dispatched in <endUpdate>. The <code>edit</code>
+ * property contains the <currentEdit>.
+ *
+ * Constructor: mxGraphModel
+ *
+ * Constructs a new graph model. If no root is specified then a new root
+ * <mxCell> with a default layer is created.
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that represents the root cell.
+ */
+function mxGraphModel(root)
+{
+ this.currentEdit = this.createUndoableEdit();
+
+ if (root != null)
+ {
+ this.setRoot(root);
+ }
+ else
+ {
+ this.clear();
+ }
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphModel.prototype = new mxEventSource();
+mxGraphModel.prototype.constructor = mxGraphModel;
+
+/**
+ * Variable: root
+ *
+ * Holds the root cell, which in turn contains the cells that represent the
+ * layers of the diagram as child cells. That is, the actual elements of the
+ * diagram are supposed to live in the third generation of cells and below.
+ */
+mxGraphModel.prototype.root = null;
+
+/**
+ * Variable: cells
+ *
+ * Maps from Ids to cells.
+ */
+mxGraphModel.prototype.cells = null;
+
+/**
+ * Variable: maintainEdgeParent
+ *
+ * Specifies if edges should automatically be moved into the nearest common
+ * ancestor of their terminals. Default is true.
+ */
+mxGraphModel.prototype.maintainEdgeParent = true;
+
+/**
+ * Variable: createIds
+ *
+ * Specifies if the model should automatically create Ids for new cells.
+ * Default is true.
+ */
+mxGraphModel.prototype.createIds = true;
+
+/**
+ * Variable: prefix
+ *
+ * Defines the prefix of new Ids. Default is an empty string.
+ */
+mxGraphModel.prototype.prefix = '';
+
+/**
+ * Variable: postfix
+ *
+ * Defines the postfix of new Ids. Default is an empty string.
+ */
+mxGraphModel.prototype.postfix = '';
+
+/**
+ * Variable: nextId
+ *
+ * Specifies the next Id to be created. Initial value is 0.
+ */
+mxGraphModel.prototype.nextId = 0;
+
+/**
+ * Variable: currentEdit
+ *
+ * Holds the changes for the current transaction. If the transaction is
+ * closed then a new object is created for this variable using
+ * <createUndoableEdit>.
+ */
+mxGraphModel.prototype.currentEdit = null;
+
+/**
+ * Variable: updateLevel
+ *
+ * Counter for the depth of nested transactions. Each call to <beginUpdate>
+ * will increment this number and each call to <endUpdate> will decrement
+ * it. When the counter reaches 0, the transaction is closed and the
+ * respective events are fired. Initial value is 0.
+ */
+mxGraphModel.prototype.updateLevel = 0;
+
+/**
+ * Variable: endingUpdate
+ *
+ * True if the program flow is currently inside endUpdate.
+ */
+mxGraphModel.prototype.endingUpdate = false;
+
+/**
+ * Function: clear
+ *
+ * Sets a new root using <createRoot>.
+ */
+mxGraphModel.prototype.clear = function()
+{
+ this.setRoot(this.createRoot());
+};
+
+/**
+ * Function: isCreateIds
+ *
+ * Returns <createIds>.
+ */
+mxGraphModel.prototype.isCreateIds = function()
+{
+ return this.createIds;
+};
+
+/**
+ * Function: setCreateIds
+ *
+ * Sets <createIds>.
+ */
+mxGraphModel.prototype.setCreateIds = function(value)
+{
+ this.createIds = value;
+};
+
+/**
+ * Function: createRoot
+ *
+ * Creates a new root cell with a default layer (child 0).
+ */
+mxGraphModel.prototype.createRoot = function()
+{
+ var cell = new mxCell();
+ cell.insert(new mxCell());
+
+ return cell;
+};
+
+/**
+ * Function: getCell
+ *
+ * Returns the <mxCell> for the specified Id or null if no cell can be
+ * found for the given Id.
+ *
+ * Parameters:
+ *
+ * id - A string representing the Id of the cell.
+ */
+mxGraphModel.prototype.getCell = function(id)
+{
+ return (this.cells != null) ? this.cells[id] : null;
+};
+
+/**
+ * Function: filterCells
+ *
+ * Returns the cells from the given array where the fiven filter function
+ * returns true.
+ */
+mxGraphModel.prototype.filterCells = function(cells, filter)
+{
+ var result = null;
+
+ if (cells != null)
+ {
+ result = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (filter(cells[i]))
+ {
+ result.push(cells[i]);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getDescendants
+ *
+ * Returns all descendants of the given cell and the cell itself in an array.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose descendants should be returned.
+ */
+mxGraphModel.prototype.getDescendants = function(parent)
+{
+ return this.filterDescendants(null, parent);
+};
+
+/**
+ * Function: filterDescendants
+ *
+ * Visits all cells recursively and applies the specified filter function
+ * to each cell. If the function returns true then the cell is added
+ * to the resulting array. The parent and result paramters are optional.
+ * If parent is not specified then the recursion starts at <root>.
+ *
+ * Example:
+ * The following example extracts all vertices from a given model:
+ * (code)
+ * var filter = function(cell)
+ * {
+ * return model.isVertex(cell);
+ * }
+ * var vertices = model.filterDescendants(filter);
+ * (code)
+ *
+ * Parameters:
+ *
+ * filter - JavaScript function that takes an <mxCell> as an argument
+ * and returns a boolean.
+ * parent - Optional <mxCell> that is used as the root of the recursion.
+ */
+mxGraphModel.prototype.filterDescendants = function(filter, parent)
+{
+ // Creates a new array for storing the result
+ var result = [];
+
+ // Recursion starts at the root of the model
+ parent = parent || this.getRoot();
+
+ // Checks if the filter returns true for the cell
+ // and adds it to the result array
+ if (filter == null || filter(parent))
+ {
+ result.push(parent);
+ }
+
+ // Visits the children of the cell
+ var childCount = this.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.getChildAt(parent, i);
+ result = result.concat(this.filterDescendants(filter, child));
+ }
+
+ return result;
+};
+
+/**
+ * Function: getRoot
+ *
+ * Returns the root of the model or the topmost parent of the given cell.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> that specifies the child.
+ */
+mxGraphModel.prototype.getRoot = function(cell)
+{
+ var root = cell || this.root;
+
+ if (cell != null)
+ {
+ while (cell != null)
+ {
+ root = cell;
+ cell = this.getParent(cell);
+ }
+ }
+
+ return root;
+};
+
+/**
+ * Function: setRoot
+ *
+ * Sets the <root> of the model using <mxRootChange> and adds the change to
+ * the current transaction. This resets all datastructures in the model and
+ * is the preferred way of clearing an existing model. Returns the new
+ * root.
+ *
+ * Example:
+ *
+ * (code)
+ * var root = new mxCell();
+ * root.insert(new mxCell());
+ * model.setRoot(root);
+ * (end)
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that specifies the new root.
+ */
+mxGraphModel.prototype.setRoot = function(root)
+{
+ this.execute(new mxRootChange(this, root));
+
+ return root;
+};
+
+/**
+ * Function: rootChanged
+ *
+ * Inner callback to change the root of the model and update the internal
+ * datastructures, such as <cells> and <nextId>. Returns the previous root.
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that specifies the new root.
+ */
+mxGraphModel.prototype.rootChanged = function(root)
+{
+ var oldRoot = this.root;
+ this.root = root;
+
+ // Resets counters and datastructures
+ this.nextId = 0;
+ this.cells = null;
+ this.cellAdded(root);
+
+ return oldRoot;
+};
+
+/**
+ * Function: isRoot
+ *
+ * Returns true if the given cell is the root of the model and a non-null
+ * value.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible root.
+ */
+mxGraphModel.prototype.isRoot = function(cell)
+{
+ return cell != null && this.root == cell;
+};
+
+/**
+ * Function: isLayer
+ *
+ * Returns true if <isRoot> returns true for the parent of the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible layer.
+ */
+mxGraphModel.prototype.isLayer = function(cell)
+{
+ return this.isRoot(this.getParent(cell));
+};
+
+/**
+ * Function: isAncestor
+ *
+ * Returns true if the given parent is an ancestor of the given child.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that specifies the parent.
+ * child - <mxCell> that specifies the child.
+ */
+mxGraphModel.prototype.isAncestor = function(parent, child)
+{
+ while (child != null && child != parent)
+ {
+ child = this.getParent(child);
+ }
+
+ return child == parent;
+};
+
+/**
+ * Function: contains
+ *
+ * Returns true if the model contains the given <mxCell>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell.
+ */
+mxGraphModel.prototype.contains = function(cell)
+{
+ return this.isAncestor(this.root, cell);
+};
+
+/**
+ * Function: getParent
+ *
+ * Returns the parent of the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose parent should be returned.
+ */
+mxGraphModel.prototype.getParent = function(cell)
+{
+ return (cell != null) ? cell.getParent() : null;
+};
+
+/**
+ * Function: add
+ *
+ * Adds the specified child to the parent at the given index using
+ * <mxChildChange> and adds the change to the current transaction. If no
+ * index is specified then the child is appended to the parent's array of
+ * children. Returns the inserted child.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that specifies the parent to contain the child.
+ * child - <mxCell> that specifies the child to be inserted.
+ * index - Optional integer that specifies the index of the child.
+ */
+mxGraphModel.prototype.add = function(parent, child, index)
+{
+ if (child != parent && parent != null && child != null)
+ {
+ // Appends the child if no index was specified
+ if (index == null)
+ {
+ index = this.getChildCount(parent);
+ }
+
+ var parentChanged = parent != this.getParent(child);
+ this.execute(new mxChildChange(this, parent, child, index));
+
+ // Maintains the edges parents by moving the edges
+ // into the nearest common ancestor of its
+ // terminals
+ if (this.maintainEdgeParent && parentChanged)
+ {
+ this.updateEdgeParents(child);
+ }
+ }
+
+ return child;
+};
+
+/**
+ * Function: cellAdded
+ *
+ * Inner callback to update <cells> when a cell has been added. This
+ * implementation resolves collisions by creating new Ids. To change the
+ * ID of a cell after it was inserted into the model, use the following
+ * code:
+ *
+ * (code
+ * delete model.cells[cell.getId()];
+ * cell.setId(newId);
+ * model.cells[cell.getId()] = cell;
+ * (end)
+ *
+ * If the change of the ID should be part of the command history, then the
+ * cell should be removed from the model and a clone with the new ID should
+ * be reinserted into the model instead.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell that has been added.
+ */
+mxGraphModel.prototype.cellAdded = function(cell)
+{
+ if (cell != null)
+ {
+ // Creates an Id for the cell if not Id exists
+ if (cell.getId() == null && this.createIds)
+ {
+ cell.setId(this.createId(cell));
+ }
+
+ if (cell.getId() != null)
+ {
+ var collision = this.getCell(cell.getId());
+
+ if (collision != cell)
+ {
+ // Creates new Id for the cell
+ // as long as there is a collision
+ while (collision != null)
+ {
+ cell.setId(this.createId(cell));
+ collision = this.getCell(cell.getId());
+ }
+
+ // Lazily creates the cells dictionary
+ if (this.cells == null)
+ {
+ this.cells = new Object();
+ }
+
+ this.cells[cell.getId()] = cell;
+ }
+ }
+
+ // Makes sure IDs of deleted cells are not reused
+ if (mxUtils.isNumeric(cell.getId()))
+ {
+ this.nextId = Math.max(this.nextId, cell.getId());
+ }
+
+ // Recursively processes child cells
+ var childCount = this.getChildCount(cell);
+
+ for (var i=0; i<childCount; i++)
+ {
+ this.cellAdded(this.getChildAt(cell, i));
+ }
+ }
+};
+
+/**
+ * Function: createId
+ *
+ * Hook method to create an Id for the specified cell. This implementation
+ * concatenates <prefix>, id and <postfix> to create the Id and increments
+ * <nextId>. The cell is ignored by this implementation, but can be used in
+ * overridden methods to prefix the Ids with eg. the cell type.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to create the Id for.
+ */
+mxGraphModel.prototype.createId = function(cell)
+{
+ var id = this.nextId;
+ this.nextId++;
+
+ return this.prefix + id + this.postfix;
+};
+
+/**
+ * Function: updateEdgeParents
+ *
+ * Updates the parent for all edges that are connected to cell or one of
+ * its descendants using <updateEdgeParent>.
+ */
+mxGraphModel.prototype.updateEdgeParents = function(cell, root)
+{
+ // Gets the topmost node of the hierarchy
+ root = root || this.getRoot(cell);
+
+ // Updates edges on children first
+ var childCount = this.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.getChildAt(cell, i);
+ this.updateEdgeParents(child, root);
+ }
+
+ // Updates the parents of all connected edges
+ var edgeCount = this.getEdgeCount(cell);
+ var edges = [];
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ edges.push(this.getEdgeAt(cell, i));
+ }
+
+ for (var i = 0; i < edges.length; i++)
+ {
+ var edge = edges[i];
+
+ // Updates edge parent if edge and child have
+ // a common root node (does not need to be the
+ // model root node)
+ if (this.isAncestor(root, edge))
+ {
+ this.updateEdgeParent(edge, root);
+ }
+ }
+};
+
+/**
+ * Function: updateEdgeParent
+ *
+ * Inner callback to update the parent of the specified <mxCell> to the
+ * nearest-common-ancestor of its two terminals.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that specifies the edge.
+ * root - <mxCell> that represents the current root of the model.
+ */
+mxGraphModel.prototype.updateEdgeParent = function(edge, root)
+{
+ var source = this.getTerminal(edge, true);
+ var target = this.getTerminal(edge, false);
+ var cell = null;
+
+ // Uses the first non-relative descendants of the source terminal
+ while (source != null && !this.isEdge(source) &&
+ source.geometry != null && source.geometry.relative)
+ {
+ source = this.getParent(source);
+ }
+
+ // Uses the first non-relative descendants of the target terminal
+ while (target != null && !this.isEdge(target) &&
+ target.geometry != null && target.geometry.relative)
+ {
+ target = this.getParent(target);
+ }
+
+ if (this.isAncestor(root, source) && this.isAncestor(root, target))
+ {
+ if (source == target)
+ {
+ cell = this.getParent(source);
+ }
+ else
+ {
+ cell = this.getNearestCommonAncestor(source, target);
+ }
+
+ if (cell != null && (this.getParent(cell) != this.root ||
+ this.isAncestor(cell, edge)) && this.getParent(edge) != cell)
+ {
+ var geo = this.getGeometry(edge);
+
+ if (geo != null)
+ {
+ var origin1 = this.getOrigin(this.getParent(edge));
+ var origin2 = this.getOrigin(cell);
+
+ var dx = origin2.x - origin1.x;
+ var dy = origin2.y - origin1.y;
+
+ geo = geo.clone();
+ geo.translate(-dx, -dy);
+ this.setGeometry(edge, geo);
+ }
+
+ this.add(cell, edge, this.getChildCount(cell));
+ }
+ }
+};
+
+/**
+ * Function: getOrigin
+ *
+ * Returns the absolute, accumulated origin for the children inside the
+ * given parent as an <mxPoint>.
+ */
+mxGraphModel.prototype.getOrigin = function(cell)
+{
+ var result = null;
+
+ if (cell != null)
+ {
+ result = this.getOrigin(this.getParent(cell));
+
+ if (!this.isEdge(cell))
+ {
+ var geo = this.getGeometry(cell);
+
+ if (geo != null)
+ {
+ result.x += geo.x;
+ result.y += geo.y;
+ }
+ }
+ }
+ else
+ {
+ result = new mxPoint();
+ }
+
+ return result;
+};
+
+/**
+ * Function: getNearestCommonAncestor
+ *
+ * Returns the nearest common ancestor for the specified cells.
+ *
+ * Parameters:
+ *
+ * cell1 - <mxCell> that specifies the first cell in the tree.
+ * cell2 - <mxCell> that specifies the second cell in the tree.
+ */
+mxGraphModel.prototype.getNearestCommonAncestor = function(cell1, cell2)
+{
+ if (cell1 != null && cell2 != null)
+ {
+ // Creates the cell path for the second cell
+ var path = mxCellPath.create(cell2);
+
+ if (path != null && path.length > 0)
+ {
+ // Bubbles through the ancestors of the first
+ // cell to find the nearest common ancestor.
+ var cell = cell1;
+ var current = mxCellPath.create(cell);
+
+ // Inverts arguments
+ if (path.length < current.length)
+ {
+ cell = cell2;
+ var tmp = current;
+ current = path;
+ path = tmp;
+ }
+
+ while (cell != null)
+ {
+ var parent = this.getParent(cell);
+
+ // Checks if the cell path is equal to the beginning of the given cell path
+ if (path.indexOf(current + mxCellPath.PATH_SEPARATOR) == 0 && parent != null)
+ {
+ return cell;
+ }
+
+ current = mxCellPath.getParentPath(current);
+ cell = parent;
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the specified cell from the model using <mxChildChange> and adds
+ * the change to the current transaction. This operation will remove the
+ * cell and all of its children from the model. Returns the removed cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be removed.
+ */
+mxGraphModel.prototype.remove = function(cell)
+{
+ if (cell == this.root)
+ {
+ this.setRoot(null);
+ }
+ else if (this.getParent(cell) != null)
+ {
+ this.execute(new mxChildChange(this, null, cell));
+ }
+
+ return cell;
+};
+
+/**
+ * Function: cellRemoved
+ *
+ * Inner callback to update <cells> when a cell has been removed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell that has been removed.
+ */
+mxGraphModel.prototype.cellRemoved = function(cell)
+{
+ if (cell != null && this.cells != null)
+ {
+ // Recursively processes child cells
+ var childCount = this.getChildCount(cell);
+
+ for (var i = childCount - 1; i >= 0; i--)
+ {
+ this.cellRemoved(this.getChildAt(cell, i));
+ }
+
+ // Removes the dictionary entry for the cell
+ if (this.cells != null && cell.getId() != null)
+ {
+ delete this.cells[cell.getId()];
+ }
+ }
+};
+
+/**
+ * Function: parentForCellChanged
+ *
+ * Inner callback to update the parent of a cell using <mxCell.insert>
+ * on the parent and return the previous parent.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to update the parent for.
+ * parent - <mxCell> that specifies the new parent of the cell.
+ * index - Optional integer that defines the index of the child
+ * in the parent's child array.
+ */
+mxGraphModel.prototype.parentForCellChanged = function(cell, parent, index)
+{
+ var previous = this.getParent(cell);
+
+ if (parent != null)
+ {
+ if (parent != previous || previous.getIndex(cell) != index)
+ {
+ parent.insert(cell, index);
+ }
+ }
+ else if (previous != null)
+ {
+ var oldIndex = previous.getIndex(cell);
+ previous.remove(oldIndex);
+ }
+
+ // Checks if the previous parent was already in the
+ // model and avoids calling cellAdded if it was.
+ if (!this.contains(previous) && parent != null)
+ {
+ this.cellAdded(cell);
+ }
+ else if (parent == null)
+ {
+ this.cellRemoved(cell);
+ }
+
+ return previous;
+};
+
+/**
+ * Function: getChildCount
+ *
+ * Returns the number of children in the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose number of children should be returned.
+ */
+mxGraphModel.prototype.getChildCount = function(cell)
+{
+ return (cell != null) ? cell.getChildCount() : 0;
+};
+
+/**
+ * Function: getChildAt
+ *
+ * Returns the child of the given <mxCell> at the given index.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the parent.
+ * index - Integer that specifies the index of the child to be returned.
+ */
+mxGraphModel.prototype.getChildAt = function(cell, index)
+{
+ return (cell != null) ? cell.getChildAt(index) : null;
+};
+
+/**
+ * Function: getChildren
+ *
+ * Returns all children of the given <mxCell> as an array of <mxCells>. The
+ * return value should be only be read.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> the represents the parent.
+ */
+mxGraphModel.prototype.getChildren = function(cell)
+{
+ return (cell != null) ? cell.children : null;
+};
+
+/**
+ * Function: getChildVertices
+ *
+ * Returns the child vertices of the given parent.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose child vertices should be returned.
+ */
+mxGraphModel.prototype.getChildVertices = function(parent)
+{
+ return this.getChildCells(parent, true, false);
+};
+
+/**
+ * Function: getChildEdges
+ *
+ * Returns the child edges of the given parent.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose child edges should be returned.
+ */
+mxGraphModel.prototype.getChildEdges = function(parent)
+{
+ return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ *
+ * Returns the children of the given cell that are vertices and/or edges
+ * depending on the arguments.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> the represents the parent.
+ * vertices - Boolean indicating if child vertices should be returned.
+ * Default is false.
+ * edges - Boolean indicating if child edges should be returned.
+ * Default is false.
+ */
+mxGraphModel.prototype.getChildCells = function(parent, vertices, edges)
+{
+ vertices = (vertices != null) ? vertices : false;
+ edges = (edges != null) ? edges : false;
+
+ var childCount = this.getChildCount(parent);
+ var result = [];
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.getChildAt(parent, i);
+
+ if ((!edges && !vertices) || (edges && this.isEdge(child)) ||
+ (vertices && this.isVertex(child)))
+ {
+ result.push(child);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getTerminal
+ *
+ * Returns the source or target <mxCell> of the given edge depending on the
+ * value of the boolean parameter.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that specifies the edge.
+ * isSource - Boolean indicating which end of the edge should be returned.
+ */
+mxGraphModel.prototype.getTerminal = function(edge, isSource)
+{
+ return (edge != null) ? edge.getTerminal(isSource) : null;
+};
+
+/**
+ * Function: setTerminal
+ *
+ * Sets the source or target terminal of the given <mxCell> using
+ * <mxTerminalChange> and adds the change to the current transaction.
+ * This implementation updates the parent of the edge using <updateEdgeParent>
+ * if required.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that specifies the edge.
+ * terminal - <mxCell> that specifies the new terminal.
+ * isSource - Boolean indicating if the terminal is the new source or
+ * target terminal of the edge.
+ */
+mxGraphModel.prototype.setTerminal = function(edge, terminal, isSource)
+{
+ var terminalChanged = terminal != this.getTerminal(edge, isSource);
+ this.execute(new mxTerminalChange(this, edge, terminal, isSource));
+
+ if (this.maintainEdgeParent && terminalChanged)
+ {
+ this.updateEdgeParent(edge, this.getRoot());
+ }
+
+ return terminal;
+};
+
+/**
+ * Function: setTerminals
+ *
+ * Sets the source and target <mxCell> of the given <mxCell> in a single
+ * transaction using <setTerminal> for each end of the edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that specifies the edge.
+ * source - <mxCell> that specifies the new source terminal.
+ * target - <mxCell> that specifies the new target terminal.
+ */
+mxGraphModel.prototype.setTerminals = function(edge, source, target)
+{
+ this.beginUpdate();
+ try
+ {
+ this.setTerminal(edge, source, true);
+ this.setTerminal(edge, target, false);
+ }
+ finally
+ {
+ this.endUpdate();
+ }
+};
+
+/**
+ * Function: terminalForCellChanged
+ *
+ * Inner helper function to update the terminal of the edge using
+ * <mxCell.insertEdge> and return the previous terminal.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that specifies the edge to be updated.
+ * terminal - <mxCell> that specifies the new terminal.
+ * isSource - Boolean indicating if the terminal is the new source or
+ * target terminal of the edge.
+ */
+mxGraphModel.prototype.terminalForCellChanged = function(edge, terminal, isSource)
+{
+ var previous = this.getTerminal(edge, isSource);
+
+ if (terminal != null)
+ {
+ terminal.insertEdge(edge, isSource);
+ }
+ else if (previous != null)
+ {
+ previous.removeEdge(edge, isSource);
+ }
+
+ return previous;
+};
+
+/**
+ * Function: getEdgeCount
+ *
+ * Returns the number of distinct edges connected to the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the vertex.
+ */
+mxGraphModel.prototype.getEdgeCount = function(cell)
+{
+ return (cell != null) ? cell.getEdgeCount() : 0;
+};
+
+/**
+ * Function: getEdgeAt
+ *
+ * Returns the edge of cell at the given index.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the vertex.
+ * index - Integer that specifies the index of the edge
+ * to return.
+ */
+mxGraphModel.prototype.getEdgeAt = function(cell, index)
+{
+ return (cell != null) ? cell.getEdgeAt(index) : null;
+};
+
+/**
+ * Function: getDirectedEdgeCount
+ *
+ * Returns the number of incoming or outgoing edges, ignoring the given
+ * edge.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose edge count should be returned.
+ * outgoing - Boolean that specifies if the number of outgoing or
+ * incoming edges should be returned.
+ * ignoredEdge - <mxCell> that represents an edge to be ignored.
+ */
+mxGraphModel.prototype.getDirectedEdgeCount = function(cell, outgoing, ignoredEdge)
+{
+ var count = 0;
+ var edgeCount = this.getEdgeCount(cell);
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var edge = this.getEdgeAt(cell, i);
+
+ if (edge != ignoredEdge && this.getTerminal(edge, outgoing) == cell)
+ {
+ count++;
+ }
+ }
+
+ return count;
+};
+
+/**
+ * Function: getConnections
+ *
+ * Returns all edges of the given cell without loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose edges should be returned.
+ *
+ */
+mxGraphModel.prototype.getConnections = function(cell)
+{
+ return this.getEdges(cell, true, true, false);
+};
+
+/**
+ * Function: getIncomingEdges
+ *
+ * Returns the incoming edges of the given cell without loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose incoming edges should be returned.
+ *
+ */
+mxGraphModel.prototype.getIncomingEdges = function(cell)
+{
+ return this.getEdges(cell, true, false, false);
+};
+
+/**
+ * Function: getOutgoingEdges
+ *
+ * Returns the outgoing edges of the given cell without loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose outgoing edges should be returned.
+ *
+ */
+mxGraphModel.prototype.getOutgoingEdges = function(cell)
+{
+ return this.getEdges(cell, false, true, false);
+};
+
+/**
+ * Function: getEdges
+ *
+ * Returns all distinct edges connected to this cell as a new array of
+ * <mxCells>. If at least one of incoming or outgoing is true, then loops
+ * are ignored, otherwise if both are false, then all edges connected to
+ * the given cell are returned including loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell.
+ * incoming - Optional boolean that specifies if incoming edges should be
+ * returned. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should be
+ * returned. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be returned.
+ * Default is true.
+ */
+mxGraphModel.prototype.getEdges = function(cell, incoming, outgoing, includeLoops)
+{
+ incoming = (incoming != null) ? incoming : true;
+ outgoing = (outgoing != null) ? outgoing : true;
+ includeLoops = (includeLoops != null) ? includeLoops : true;
+
+ var edgeCount = this.getEdgeCount(cell);
+ var result = [];
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var edge = this.getEdgeAt(cell, i);
+ var source = this.getTerminal(edge, true);
+ var target = this.getTerminal(edge, false);
+
+ if ((includeLoops && source == target) || ((source != target) && ((incoming && target == cell) ||
+ (outgoing && source == cell))))
+ {
+ result.push(edge);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getEdgesBetween
+ *
+ * Returns all edges between the given source and target pair. If directed
+ * is true, then only edges from the source to the target are returned,
+ * otherwise, all edges between the two cells are returned.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that defines the source terminal of the edge to be
+ * returned.
+ * target - <mxCell> that defines the target terminal of the edge to be
+ * returned.
+ * directed - Optional boolean that specifies if the direction of the
+ * edge should be taken into account. Default is false.
+ */
+mxGraphModel.prototype.getEdgesBetween = function(source, target, directed)
+{
+ directed = (directed != null) ? directed : false;
+
+ var tmp1 = this.getEdgeCount(source);
+ var tmp2 = this.getEdgeCount(target);
+
+ // Assumes the source has less connected edges
+ var terminal = source;
+ var edgeCount = tmp1;
+
+ // Uses the smaller array of connected edges
+ // for searching the edge
+ if (tmp2 < tmp1)
+ {
+ edgeCount = tmp2;
+ terminal = target;
+ }
+
+ var result = [];
+
+ // Checks if the edge is connected to the correct
+ // cell and returns the first match
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var edge = this.getEdgeAt(terminal, i);
+ var src = this.getTerminal(edge, true);
+ var trg = this.getTerminal(edge, false);
+ var directedMatch = (src == source) && (trg == target);
+ var oppositeMatch = (trg == source) && (src == target);
+
+ if (directedMatch || (!directed && oppositeMatch))
+ {
+ result.push(edge);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getOpposites
+ *
+ * Returns all opposite vertices wrt terminal for the given edges, only
+ * returning sources and/or targets as specified. The result is returned
+ * as an array of <mxCells>.
+ *
+ * Parameters:
+ *
+ * edges - Array of <mxCells> that contain the edges to be examined.
+ * terminal - <mxCell> that specifies the known end of the edges.
+ * sources - Boolean that specifies if source terminals should be contained
+ * in the result. Default is true.
+ * targets - Boolean that specifies if target terminals should be contained
+ * in the result. Default is true.
+ */
+mxGraphModel.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+ sources = (sources != null) ? sources : true;
+ targets = (targets != null) ? targets : true;
+
+ var terminals = [];
+
+ if (edges != null)
+ {
+ for (var i = 0; i < edges.length; i++)
+ {
+ var source = this.getTerminal(edges[i], true);
+ var target = this.getTerminal(edges[i], false);
+
+ // Checks if the terminal is the source of
+ // the edge and if the target should be
+ // stored in the result
+ if (source == terminal && target != null && target != terminal && targets)
+ {
+ terminals.push(target);
+ }
+
+ // Checks if the terminal is the taget of
+ // the edge and if the source should be
+ // stored in the result
+ else if (target == terminal && source != null && source != terminal && sources)
+ {
+ terminals.push(source);
+ }
+ }
+ }
+
+ return terminals;
+};
+
+/**
+ * Function: getTopmostCells
+ *
+ * Returns the topmost cells of the hierarchy in an array that contains no
+ * descendants for each <mxCell> that it contains. Duplicates should be
+ * removed in the cells array to improve performance.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose topmost ancestors should be returned.
+ */
+mxGraphModel.prototype.getTopmostCells = function(cells)
+{
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var cell = cells[i];
+ var topmost = true;
+ var parent = this.getParent(cell);
+
+ while (parent != null)
+ {
+ if (mxUtils.indexOf(cells, parent) >= 0)
+ {
+ topmost = false;
+ break;
+ }
+
+ parent = this.getParent(parent);
+ }
+
+ if (topmost)
+ {
+ tmp.push(cell);
+ }
+ }
+
+ return tmp;
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns true if the given cell is a vertex.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible vertex.
+ */
+mxGraphModel.prototype.isVertex = function(cell)
+{
+ return (cell != null) ? cell.isVertex() : false;
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns true if the given cell is an edge.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible edge.
+ */
+mxGraphModel.prototype.isEdge = function(cell)
+{
+ return (cell != null) ? cell.isEdge() : false;
+};
+
+/**
+ * Function: isConnectable
+ *
+ * Returns true if the given <mxCell> is connectable. If <edgesConnectable>
+ * is false, then this function returns false for all edges else it returns
+ * the return value of <mxCell.isConnectable>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraphModel.prototype.isConnectable = function(cell)
+{
+ return (cell != null) ? cell.isConnectable() : false;
+};
+
+/**
+ * Function: getValue
+ *
+ * Returns the user object of the given <mxCell> using <mxCell.getValue>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose user object should be returned.
+ */
+mxGraphModel.prototype.getValue = function(cell)
+{
+ return (cell != null) ? cell.getValue() : null;
+};
+
+/**
+ * Function: setValue
+ *
+ * Sets the user object of then given <mxCell> using <mxValueChange>
+ * and adds the change to the current transaction.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose user object should be changed.
+ * value - Object that defines the new user object.
+ */
+mxGraphModel.prototype.setValue = function(cell, value)
+{
+ this.execute(new mxValueChange(this, cell, value));
+
+ return value;
+};
+
+/**
+ * Function: valueForCellChanged
+ *
+ * Inner callback to update the user object of the given <mxCell>
+ * using <mxCell.valueChanged> and return the previous value,
+ * that is, the return value of <mxCell.valueChanged>.
+ *
+ * To change a specific attribute in an XML node, the following code can be
+ * used.
+ *
+ * (code)
+ * graph.getModel().valueForCellChanged = function(cell, value)
+ * {
+ * var previous = cell.value.getAttribute('label');
+ * cell.value.setAttribute('label', value);
+ *
+ * return previous;
+ * };
+ * (end)
+ */
+mxGraphModel.prototype.valueForCellChanged = function(cell, value)
+{
+ return cell.valueChanged(value);
+};
+
+/**
+ * Function: getGeometry
+ *
+ * Returns the <mxGeometry> of the given <mxCell>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraphModel.prototype.getGeometry = function(cell, geometry)
+{
+ return (cell != null) ? cell.getGeometry() : null;
+};
+
+/**
+ * Function: setGeometry
+ *
+ * Sets the <mxGeometry> of the given <mxCell>. The actual update
+ * of the cell is carried out in <geometryForCellChanged>. The
+ * <mxGeometryChange> action is used to encapsulate the change.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose geometry should be changed.
+ * geometry - <mxGeometry> that defines the new geometry.
+ */
+mxGraphModel.prototype.setGeometry = function(cell, geometry)
+{
+ if (geometry != this.getGeometry(cell))
+ {
+ this.execute(new mxGeometryChange(this, cell, geometry));
+ }
+
+ return geometry;
+};
+
+/**
+ * Function: geometryForCellChanged
+ *
+ * Inner callback to update the <mxGeometry> of the given <mxCell> using
+ * <mxCell.setGeometry> and return the previous <mxGeometry>.
+ */
+mxGraphModel.prototype.geometryForCellChanged = function(cell, geometry)
+{
+ var previous = this.getGeometry(cell);
+ cell.setGeometry(geometry);
+
+ return previous;
+};
+
+/**
+ * Function: getStyle
+ *
+ * Returns the style of the given <mxCell>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose style should be returned.
+ */
+mxGraphModel.prototype.getStyle = function(cell)
+{
+ return (cell != null) ? cell.getStyle() : null;
+};
+
+/**
+ * Function: setStyle
+ *
+ * Sets the style of the given <mxCell> using <mxStyleChange> and
+ * adds the change to the current transaction.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose style should be changed.
+ * style - String of the form [stylename;|key=value;] to specify
+ * the new cell style.
+ */
+mxGraphModel.prototype.setStyle = function(cell, style)
+{
+ if (style != this.getStyle(cell))
+ {
+ this.execute(new mxStyleChange(this, cell, style));
+ }
+
+ return style;
+};
+
+/**
+ * Function: styleForCellChanged
+ *
+ * Inner callback to update the style of the given <mxCell>
+ * using <mxCell.setStyle> and return the previous style.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell to be updated.
+ * style - String of the form [stylename;|key=value;] to specify
+ * the new cell style.
+ */
+mxGraphModel.prototype.styleForCellChanged = function(cell, style)
+{
+ var previous = this.getStyle(cell);
+ cell.setStyle(style);
+
+ return previous;
+};
+
+/**
+ * Function: isCollapsed
+ *
+ * Returns true if the given <mxCell> is collapsed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraphModel.prototype.isCollapsed = function(cell)
+{
+ return (cell != null) ? cell.isCollapsed() : false;
+};
+
+/**
+ * Function: setCollapsed
+ *
+ * Sets the collapsed state of the given <mxCell> using <mxCollapseChange>
+ * and adds the change to the current transaction.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose collapsed state should be changed.
+ * collapsed - Boolean that specifies the new collpased state.
+ */
+mxGraphModel.prototype.setCollapsed = function(cell, collapsed)
+{
+ if (collapsed != this.isCollapsed(cell))
+ {
+ this.execute(new mxCollapseChange(this, cell, collapsed));
+ }
+
+ return collapsed;
+};
+
+/**
+ * Function: collapsedStateForCellChanged
+ *
+ * Inner callback to update the collapsed state of the
+ * given <mxCell> using <mxCell.setCollapsed> and return
+ * the previous collapsed state.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell to be updated.
+ * collapsed - Boolean that specifies the new collpased state.
+ */
+mxGraphModel.prototype.collapsedStateForCellChanged = function(cell, collapsed)
+{
+ var previous = this.isCollapsed(cell);
+ cell.setCollapsed(collapsed);
+
+ return previous;
+};
+
+/**
+ * Function: isVisible
+ *
+ * Returns true if the given <mxCell> is visible.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraphModel.prototype.isVisible = function(cell)
+{
+ return (cell != null) ? cell.isVisible() : false;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Sets the visible state of the given <mxCell> using <mxVisibleChange> and
+ * adds the change to the current transaction.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose visible state should be changed.
+ * visible - Boolean that specifies the new visible state.
+ */
+mxGraphModel.prototype.setVisible = function(cell, visible)
+{
+ if (visible != this.isVisible(cell))
+ {
+ this.execute(new mxVisibleChange(this, cell, visible));
+ }
+
+ return visible;
+};
+
+/**
+ * Function: visibleStateForCellChanged
+ *
+ * Inner callback to update the visible state of the
+ * given <mxCell> using <mxCell.setCollapsed> and return
+ * the previous visible state.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell to be updated.
+ * visible - Boolean that specifies the new visible state.
+ */
+mxGraphModel.prototype.visibleStateForCellChanged = function(cell, visible)
+{
+ var previous = this.isVisible(cell);
+ cell.setVisible(visible);
+
+ return previous;
+};
+
+/**
+ * Function: execute
+ *
+ * Executes the given edit and fires events if required. The edit object
+ * requires an execute function which is invoked. The edit is added to the
+ * <currentEdit> between <beginUpdate> and <endUpdate> calls, so that
+ * events will be fired if this execute is an individual transaction, that
+ * is, if no previous <beginUpdate> calls have been made without calling
+ * <endUpdate>. This implementation fires an <execute> event before
+ * executing the given change.
+ *
+ * Parameters:
+ *
+ * change - Object that described the change.
+ */
+mxGraphModel.prototype.execute = function(change)
+{
+ change.execute();
+ this.beginUpdate();
+ this.currentEdit.add(change);
+ this.fireEvent(new mxEventObject(mxEvent.EXECUTE, 'change', change));
+ this.endUpdate();
+};
+
+/**
+ * Function: beginUpdate
+ *
+ * Increments the <updateLevel> by one. The event notification
+ * is queued until <updateLevel> reaches 0 by use of
+ * <endUpdate>.
+ *
+ * All changes on <mxGraphModel> are transactional,
+ * that is, they are executed in a single undoable change
+ * on the model (without transaction isolation).
+ * Therefore, if you want to combine any
+ * number of changes into a single undoable change,
+ * you should group any two or more API calls that
+ * modify the graph model between <beginUpdate>
+ * and <endUpdate> calls as shown here:
+ *
+ * (code)
+ * var model = graph.getModel();
+ * var parent = graph.getDefaultParent();
+ * var index = model.getChildCount(parent);
+ * model.beginUpdate();
+ * try
+ * {
+ * model.add(parent, v1, index);
+ * model.add(parent, v2, index+1);
+ * }
+ * finally
+ * {
+ * model.endUpdate();
+ * }
+ * (end)
+ *
+ * Of course there is a shortcut for appending a
+ * sequence of cells into the default parent:
+ *
+ * (code)
+ * graph.addCells([v1, v2]).
+ * (end)
+ */
+mxGraphModel.prototype.beginUpdate = function()
+{
+ this.updateLevel++;
+ this.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE));
+};
+
+/**
+ * Function: endUpdate
+ *
+ * Decrements the <updateLevel> by one and fires an <undo>
+ * event if the <updateLevel> reaches 0. This function
+ * indirectly fires a <change> event by invoking the notify
+ * function on the <currentEdit> und then creates a new
+ * <currentEdit> using <createUndoableEdit>.
+ *
+ * The <undo> event is fired only once per edit, whereas
+ * the <change> event is fired whenever the notify
+ * function is invoked, that is, on undo and redo of
+ * the edit.
+ */
+mxGraphModel.prototype.endUpdate = function()
+{
+ this.updateLevel--;
+
+ if (!this.endingUpdate)
+ {
+ this.endingUpdate = this.updateLevel == 0;
+ this.fireEvent(new mxEventObject(mxEvent.END_UPDATE, 'edit', this.currentEdit));
+
+ try
+ {
+ if (this.endingUpdate && !this.currentEdit.isEmpty())
+ {
+ this.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO, 'edit', this.currentEdit));
+ var tmp = this.currentEdit;
+ this.currentEdit = this.createUndoableEdit();
+ tmp.notify();
+ this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', tmp));
+ }
+ }
+ finally
+ {
+ this.endingUpdate = false;
+ }
+ }
+};
+
+/**
+ * Function: createUndoableEdit
+ *
+ * Creates a new <mxUndoableEdit> that implements the
+ * notify function to fire a <change> and <notify> event
+ * through the <mxUndoableEdit>'s source.
+ */
+mxGraphModel.prototype.createUndoableEdit = function()
+{
+ var edit = new mxUndoableEdit(this, true);
+
+ edit.notify = function()
+ {
+ // LATER: Remove changes property (deprecated)
+ edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ 'edit', edit, 'changes', edit.changes));
+ edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ 'edit', edit, 'changes', edit.changes));
+ };
+
+ return edit;
+};
+
+/**
+ * Function: mergeChildren
+ *
+ * Merges the children of the given cell into the given target cell inside
+ * this model. All cells are cloned unless there is a corresponding cell in
+ * the model with the same id, in which case the source cell is ignored and
+ * all edges are connected to the corresponding cell in this model. Edges
+ * are considered to have no identity and are always cloned unless the
+ * cloneAllEdges flag is set to false, in which case edges with the same
+ * id in the target model are reconnected to reflect the terminals of the
+ * source edges.
+ */
+mxGraphModel.prototype.mergeChildren = function(from, to, cloneAllEdges)
+{
+ cloneAllEdges = (cloneAllEdges != null) ? cloneAllEdges : true;
+
+ this.beginUpdate();
+ try
+ {
+ var mapping = new Object();
+ this.mergeChildrenImpl(from, to, cloneAllEdges, mapping);
+
+ // Post-processes all edges in the mapping and
+ // reconnects the terminals to the corresponding
+ // cells in the target model
+ for (var key in mapping)
+ {
+ var cell = mapping[key];
+ var terminal = this.getTerminal(cell, true);
+
+ if (terminal != null)
+ {
+ terminal = mapping[mxCellPath.create(terminal)];
+ this.setTerminal(cell, terminal, true);
+ }
+
+ terminal = this.getTerminal(cell, false);
+
+ if (terminal != null)
+ {
+ terminal = mapping[mxCellPath.create(terminal)];
+ this.setTerminal(cell, terminal, false);
+ }
+ }
+ }
+ finally
+ {
+ this.endUpdate();
+ }
+};
+
+/**
+ * Function: mergeChildren
+ *
+ * Clones the children of the source cell into the given target cell in
+ * this model and adds an entry to the mapping that maps from the source
+ * cell to the target cell with the same id or the clone of the source cell
+ * that was inserted into this model.
+ */
+mxGraphModel.prototype.mergeChildrenImpl = function(from, to, cloneAllEdges, mapping)
+{
+ this.beginUpdate();
+ try
+ {
+ var childCount = from.getChildCount();
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cell = from.getChildAt(i);
+
+ if (typeof(cell.getId) == 'function')
+ {
+ var id = cell.getId();
+ var target = (id != null && (!this.isEdge(cell) || !cloneAllEdges)) ?
+ this.getCell(id) : null;
+
+ // Clones and adds the child if no cell exists for the id
+ if (target == null)
+ {
+ var clone = cell.clone();
+ clone.setId(id);
+
+ // Sets the terminals from the original cell to the clone
+ // because the lookup uses strings not cells in JS
+ clone.setTerminal(cell.getTerminal(true), true);
+ clone.setTerminal(cell.getTerminal(false), false);
+
+ // Do *NOT* use model.add as this will move the edge away
+ // from the parent in updateEdgeParent if maintainEdgeParent
+ // is enabled in the target model
+ target = to.insert(clone);
+ this.cellAdded(target);
+ }
+
+ // Stores the mapping for later reconnecting edges
+ mapping[mxCellPath.create(cell)] = target;
+
+ // Recurses
+ this.mergeChildrenImpl(cell, target, cloneAllEdges, mapping);
+ }
+ }
+ }
+ finally
+ {
+ this.endUpdate();
+ }
+};
+
+/**
+ * Function: getParents
+ *
+ * Returns an array that represents the set (no duplicates) of all parents
+ * for the given array of cells.
+ *
+ * Parameters:
+ *
+ * cells - Array of cells whose parents should be returned.
+ */
+mxGraphModel.prototype.getParents = function(cells)
+{
+ var parents = [];
+
+ if (cells != null)
+ {
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var parent = this.getParent(cells[i]);
+
+ if (parent != null)
+ {
+ var id = mxCellPath.create(parent);
+
+ if (hash[id] == null)
+ {
+ hash[id] = parent;
+ parents.push(parent);
+ }
+ }
+ }
+ }
+
+ return parents;
+};
+
+//
+// Cell Cloning
+//
+
+/**
+ * Function: cloneCell
+ *
+ * Returns a deep clone of the given <mxCell> (including
+ * the children) which is created using <cloneCells>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be cloned.
+ */
+mxGraphModel.prototype.cloneCell = function(cell)
+{
+ if (cell != null)
+ {
+ return this.cloneCells([cell], true)[0];
+ }
+
+ return null;
+};
+
+/**
+ * Function: cloneCells
+ *
+ * Returns an array of clones for the given array of <mxCells>.
+ * Depending on the value of includeChildren, a deep clone is created for
+ * each cell. Connections are restored based if the corresponding
+ * cell is contained in the passed in array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCell> to be cloned.
+ * includeChildren - Boolean indicating if the cells should be cloned
+ * with all descendants.
+ */
+mxGraphModel.prototype.cloneCells = function(cells, includeChildren)
+{
+ var mapping = new Object();
+ var clones = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != null)
+ {
+ clones.push(this.cloneCellImpl(cells[i], mapping, includeChildren));
+ }
+ else
+ {
+ clones.push(null);
+ }
+ }
+
+ for (var i = 0; i < clones.length; i++)
+ {
+ if (clones[i] != null)
+ {
+ this.restoreClone(clones[i], cells[i], mapping);
+ }
+ }
+
+ return clones;
+};
+
+/**
+ * Function: cloneCellImpl
+ *
+ * Inner helper method for cloning cells recursively.
+ */
+mxGraphModel.prototype.cloneCellImpl = function(cell, mapping, includeChildren)
+{
+ var clone = this.cellCloned(cell);
+
+ // Stores the clone in the lookup under the
+ // cell path for the original cell
+ mapping[mxObjectIdentity.get(cell)] = clone;
+
+ if (includeChildren)
+ {
+ var childCount = this.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cloneChild = this.cloneCellImpl(
+ this.getChildAt(cell, i), mapping, true);
+ clone.insert(cloneChild);
+ }
+ }
+
+ return clone;
+};
+
+/**
+ * Function: cellCloned
+ *
+ * Hook for cloning the cell. This returns cell.clone() or
+ * any possible exceptions.
+ */
+mxGraphModel.prototype.cellCloned = function(cell)
+{
+ return cell.clone();
+};
+
+/**
+ * Function: restoreClone
+ *
+ * Inner helper method for restoring the connections in
+ * a network of cloned cells.
+ */
+mxGraphModel.prototype.restoreClone = function(clone, cell, mapping)
+{
+ var source = this.getTerminal(cell, true);
+
+ if (source != null)
+ {
+ var tmp = mapping[mxObjectIdentity.get(source)];
+
+ if (tmp != null)
+ {
+ tmp.insertEdge(clone, true);
+ }
+ }
+
+ var target = this.getTerminal(cell, false);
+
+ if (target != null)
+ {
+ var tmp = mapping[mxObjectIdentity.get(target)];
+
+ if (tmp != null)
+ {
+ tmp.insertEdge(clone, false);
+ }
+ }
+
+ var childCount = this.getChildCount(clone);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.restoreClone(this.getChildAt(clone, i),
+ this.getChildAt(cell, i), mapping);
+ }
+};
+
+//
+// Atomic changes
+//
+
+/**
+ * Class: mxRootChange
+ *
+ * Action to change the root in a model.
+ *
+ * Constructor: mxRootChange
+ *
+ * Constructs a change of the root in the
+ * specified model.
+ */
+function mxRootChange(model, root)
+{
+ this.model = model;
+ this.root = root;
+ this.previous = root;
+};
+
+/**
+ * Function: execute
+ *
+ * Carries out a change of the root using
+ * <mxGraphModel.rootChanged>.
+ */
+mxRootChange.prototype.execute = function()
+{
+ this.root = this.previous;
+ this.previous = this.model.rootChanged(this.previous);
+};
+
+/**
+ * Class: mxChildChange
+ *
+ * Action to add or remove a child in a model.
+ *
+ * Constructor: mxChildChange
+ *
+ * Constructs a change of a child in the
+ * specified model.
+ */
+function mxChildChange(model, parent, child, index)
+{
+ this.model = model;
+ this.parent = parent;
+ this.previous = parent;
+ this.child = child;
+ this.index = index;
+ this.previousIndex = index;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the parent of <child> using
+ * <mxGraphModel.parentForCellChanged> and
+ * removes or restores the cell's
+ * connections.
+ */
+mxChildChange.prototype.execute = function()
+{
+ var tmp = this.model.getParent(this.child);
+ var tmp2 = (tmp != null) ? tmp.getIndex(this.child) : 0;
+
+ if (this.previous == null)
+ {
+ this.connect(this.child, false);
+ }
+
+ tmp = this.model.parentForCellChanged(
+ this.child, this.previous, this.previousIndex);
+
+ if (this.previous != null)
+ {
+ this.connect(this.child, true);
+ }
+
+ this.parent = this.previous;
+ this.previous = tmp;
+ this.index = this.previousIndex;
+ this.previousIndex = tmp2;
+};
+
+/**
+ * Function: disconnect
+ *
+ * Disconnects the given cell recursively from its
+ * terminals and stores the previous terminal in the
+ * cell's terminals.
+ */
+mxChildChange.prototype.connect = function(cell, isConnect)
+{
+ isConnect = (isConnect != null) ? isConnect : true;
+
+ var source = cell.getTerminal(true);
+ var target = cell.getTerminal(false);
+
+ if (source != null)
+ {
+ if (isConnect)
+ {
+ this.model.terminalForCellChanged(cell, source, true);
+ }
+ else
+ {
+ this.model.terminalForCellChanged(cell, null, true);
+ }
+ }
+
+ if (target != null)
+ {
+ if (isConnect)
+ {
+ this.model.terminalForCellChanged(cell, target, false);
+ }
+ else
+ {
+ this.model.terminalForCellChanged(cell, null, false);
+ }
+ }
+
+ cell.setTerminal(source, true);
+ cell.setTerminal(target, false);
+
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i=0; i<childCount; i++)
+ {
+ this.connect(this.model.getChildAt(cell, i), isConnect);
+ }
+};
+
+/**
+ * Class: mxTerminalChange
+ *
+ * Action to change a terminal in a model.
+ *
+ * Constructor: mxTerminalChange
+ *
+ * Constructs a change of a terminal in the
+ * specified model.
+ */
+function mxTerminalChange(model, cell, terminal, source)
+{
+ this.model = model;
+ this.cell = cell;
+ this.terminal = terminal;
+ this.previous = terminal;
+ this.source = source;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the terminal of <cell> to <previous> using
+ * <mxGraphModel.terminalForCellChanged>.
+ */
+mxTerminalChange.prototype.execute = function()
+{
+ this.terminal = this.previous;
+ this.previous = this.model.terminalForCellChanged(
+ this.cell, this.previous, this.source);
+};
+
+/**
+ * Class: mxValueChange
+ *
+ * Action to change a user object in a model.
+ *
+ * Constructor: mxValueChange
+ *
+ * Constructs a change of a user object in the
+ * specified model.
+ */
+function mxValueChange(model, cell, value)
+{
+ this.model = model;
+ this.cell = cell;
+ this.value = value;
+ this.previous = value;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the value of <cell> to <previous> using
+ * <mxGraphModel.valueForCellChanged>.
+ */
+mxValueChange.prototype.execute = function()
+{
+ this.value = this.previous;
+ this.previous = this.model.valueForCellChanged(
+ this.cell, this.previous);
+};
+
+/**
+ * Class: mxStyleChange
+ *
+ * Action to change a cell's style in a model.
+ *
+ * Constructor: mxStyleChange
+ *
+ * Constructs a change of a style in the
+ * specified model.
+ */
+function mxStyleChange(model, cell, style)
+{
+ this.model = model;
+ this.cell = cell;
+ this.style = style;
+ this.previous = style;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the style of <cell> to <previous> using
+ * <mxGraphModel.styleForCellChanged>.
+ */
+mxStyleChange.prototype.execute = function()
+{
+ this.style = this.previous;
+ this.previous = this.model.styleForCellChanged(
+ this.cell, this.previous);
+};
+
+/**
+ * Class: mxGeometryChange
+ *
+ * Action to change a cell's geometry in a model.
+ *
+ * Constructor: mxGeometryChange
+ *
+ * Constructs a change of a geometry in the
+ * specified model.
+ */
+function mxGeometryChange(model, cell, geometry)
+{
+ this.model = model;
+ this.cell = cell;
+ this.geometry = geometry;
+ this.previous = geometry;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the geometry of <cell> ro <previous> using
+ * <mxGraphModel.geometryForCellChanged>.
+ */
+mxGeometryChange.prototype.execute = function()
+{
+ this.geometry = this.previous;
+ this.previous = this.model.geometryForCellChanged(
+ this.cell, this.previous);
+};
+
+/**
+ * Class: mxCollapseChange
+ *
+ * Action to change a cell's collapsed state in a model.
+ *
+ * Constructor: mxCollapseChange
+ *
+ * Constructs a change of a collapsed state in the
+ * specified model.
+ */
+function mxCollapseChange(model, cell, collapsed)
+{
+ this.model = model;
+ this.cell = cell;
+ this.collapsed = collapsed;
+ this.previous = collapsed;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the collapsed state of <cell> to <previous> using
+ * <mxGraphModel.collapsedStateForCellChanged>.
+ */
+mxCollapseChange.prototype.execute = function()
+{
+ this.collapsed = this.previous;
+ this.previous = this.model.collapsedStateForCellChanged(
+ this.cell, this.previous);
+};
+
+/**
+ * Class: mxVisibleChange
+ *
+ * Action to change a cell's visible state in a model.
+ *
+ * Constructor: mxVisibleChange
+ *
+ * Constructs a change of a visible state in the
+ * specified model.
+ */
+function mxVisibleChange(model, cell, visible)
+{
+ this.model = model;
+ this.cell = cell;
+ this.visible = visible;
+ this.previous = visible;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the visible state of <cell> to <previous> using
+ * <mxGraphModel.visibleStateForCellChanged>.
+ */
+mxVisibleChange.prototype.execute = function()
+{
+ this.visible = this.previous;
+ this.previous = this.model.visibleStateForCellChanged(
+ this.cell, this.previous);
+};
+
+/**
+ * Class: mxCellAttributeChange
+ *
+ * Action to change the attribute of a cell's user object.
+ * There is no method on the graph model that uses this
+ * action. To use the action, you can use the code shown
+ * in the example below.
+ *
+ * Example:
+ *
+ * To change the attributeName in the cell's user object
+ * to attributeValue, use the following code:
+ *
+ * (code)
+ * model.beginUpdate();
+ * try
+ * {
+ * var edit = new mxCellAttributeChange(
+ * cell, attributeName, attributeValue);
+ * model.execute(edit);
+ * }
+ * finally
+ * {
+ * model.endUpdate();
+ * }
+ * (end)
+ *
+ * Constructor: mxCellAttributeChange
+ *
+ * Constructs a change of a attribute of the DOM node
+ * stored as the value of the given <mxCell>.
+ */
+function mxCellAttributeChange(cell, attribute, value)
+{
+ this.cell = cell;
+ this.attribute = attribute;
+ this.value = value;
+ this.previous = value;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the attribute of the cell's user object by
+ * using <mxCell.setAttribute>.
+ */
+mxCellAttributeChange.prototype.execute = function()
+{
+ var tmp = this.cell.getAttribute(this.attribute);
+
+ if (this.previous == null)
+ {
+ this.cell.value.removeAttribute(this.attribute);
+ }
+ else
+ {
+ this.cell.setAttribute(this.attribute, this.previous);
+ }
+
+ this.previous = tmp;
+};
diff --git a/src/js/mxClient.js b/src/js/mxClient.js
new file mode 100644
index 0000000..a23b5fc
--- /dev/null
+++ b/src/js/mxClient.js
@@ -0,0 +1,643 @@
+/**
+ * $Id: mxClient.js,v 1.203 2012-07-19 15:19:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+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 1.10.4.1.
+ */
+ VERSION: '1.10.4.1',
+
+ /**
+ * Variable: IS_IE
+ *
+ * True if the current browser is Internet Explorer.
+ */
+ IS_IE: navigator.userAgent.indexOf('MSIE') >= 0,
+
+ /**
+ * Variable: IS_IE6
+ *
+ * True if the current browser is Internet Explorer 6.x.
+ */
+ IS_IE6: navigator.userAgent.indexOf('MSIE 6') >= 0,
+
+ /**
+ * Variable: IS_QUIRKS
+ *
+ * True if the current browser is Internet Explorer and it is in quirks mode.
+ */
+ IS_QUIRKS: navigator.userAgent.indexOf('MSIE') >= 0 && (document.documentMode == null || document.documentMode == 5),
+
+ /**
+ * Variable: IS_NS
+ *
+ * True if the current browser is Netscape (including Firefox).
+ */
+ IS_NS: navigator.userAgent.indexOf('Mozilla/') >= 0 &&
+ navigator.userAgent.indexOf('MSIE') < 0,
+
+ /**
+ * Variable: IS_OP
+ *
+ * True if the current browser is Opera.
+ */
+ IS_OP: navigator.userAgent.indexOf('Opera/') >= 0,
+
+ /**
+ * Variable: IS_OT
+ *
+ * True if -o-transform is available as a CSS style. This is the case
+ * for Opera browsers that use Presto/2.5 and later.
+ */
+ IS_OT: 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: navigator.userAgent.indexOf('AppleWebKit/') >= 0 &&
+ navigator.userAgent.indexOf('Chrome/') < 0,
+
+ /**
+ * Variable: IS_GC
+ *
+ * True if the current browser is Google Chrome.
+ */
+ IS_GC: navigator.userAgent.indexOf('Chrome/') >= 0,
+
+ /**
+ * 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_SVG
+ *
+ * True if the browser supports SVG.
+ */
+ IS_SVG: navigator.userAgent.indexOf('Firefox/') >= 0 || // FF and Camino
+ navigator.userAgent.indexOf('Iceweasel/') >= 0 || // Firefox on Debian
+ navigator.userAgent.indexOf('Seamonkey/') >= 0 || // Firefox-based
+ navigator.userAgent.indexOf('Iceape/') >= 0 || // Seamonkey on Debian
+ navigator.userAgent.indexOf('Galeon/') >= 0 || // Gnome Browser (old)
+ navigator.userAgent.indexOf('Epiphany/') >= 0 || // Gnome Browser (new)
+ navigator.userAgent.indexOf('AppleWebKit/') >= 0 || // Safari/Google Chrome
+ navigator.userAgent.indexOf('Gecko/') >= 0 || // Netscape/Gecko
+ navigator.userAgent.indexOf('Opera/') >= 0,
+
+
+ /**
+ * Variable: NO_FO
+ *
+ * True if foreignObject support is not available. This is the case for
+ * Opera and older SVG-based browsers. IE does not require this type
+ * of tag.
+ */
+ NO_FO: navigator.userAgent.indexOf('Firefox/1.') >= 0 ||
+ navigator.userAgent.indexOf('Iceweasel/1.') >= 0 ||
+ navigator.userAgent.indexOf('Firefox/2.') >= 0 ||
+ navigator.userAgent.indexOf('Iceweasel/2.') >= 0 ||
+ navigator.userAgent.indexOf('SeaMonkey/1.') >= 0 ||
+ navigator.userAgent.indexOf('Iceape/1.') >= 0 ||
+ navigator.userAgent.indexOf('Camino/1.') >= 0 ||
+ navigator.userAgent.indexOf('Epiphany/2.') >= 0 ||
+ navigator.userAgent.indexOf('Opera/') >= 0 ||
+ navigator.userAgent.indexOf('MSIE') >= 0 ||
+ navigator.userAgent.indexOf('Mozilla/2.') >= 0, // Safari/Google Chrome
+
+ /**
+ * Variable: IS_VML
+ *
+ * True if the browser supports VML.
+ */
+ IS_VML: navigator.appName.toUpperCase() == 'MICROSOFT INTERNET EXPLORER',
+
+ /**
+ * Variable: IS_MAC
+ *
+ * True if the client is a Mac.
+ */
+ IS_MAC: navigator.userAgent.toUpperCase().indexOf('MACINTOSH') > 0,
+
+ /**
+ * Variable: IS_TOUCH
+ *
+ * True if this client uses a touch interface (no mouse). Currently this
+ * detects IPads, IPods, IPhones and Android devices.
+ */
+ IS_TOUCH: navigator.userAgent.toUpperCase().indexOf('IPAD') > 0 ||
+ navigator.userAgent.toUpperCase().indexOf('IPOD') > 0 ||
+ navigator.userAgent.toUpperCase().indexOf('IPHONE') > 0 ||
+ navigator.userAgent.toUpperCase().indexOf('ANDROID') > 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,
+
+ /**
+ * Function: isBrowserSupported
+ *
+ * Returns true if the current browser is supported, that is, if
+ * <mxClient.IS_VML> or <mxClient.IS_SVG> is true.
+ *
+ * Example:
+ *
+ * (code)
+ * if (!mxClient.isBrowserSupported())
+ * {
+ * mxUtils.error('Browser is not supported!', 200, false);
+ * }
+ * (end)
+ */
+ isBrowserSupported: function()
+ {
+ return mxClient.IS_VML || mxClient.IS_SVG;
+ },
+
+ /**
+ * Function: link
+ *
+ * Adds a link node to the head of the document. Use this
+ * to add a stylesheet to the page as follows:
+ *
+ * (code)
+ * mxClient.link('stylesheet', filename);
+ * (end)
+ *
+ * where filename is the (relative) URL of the stylesheet. The charset
+ * is hardcoded to ISO-8859-1 and the type is text/css.
+ *
+ * Parameters:
+ *
+ * rel - String that represents the rel attribute of the link node.
+ * href - String that represents the href attribute of the link node.
+ * doc - Optional parent document of the link node.
+ */
+ link: function(rel, href, doc)
+ {
+ doc = doc || document;
+
+ // Workaround for Operation Aborted in IE6 if base tag is used in head
+ if (mxClient.IS_IE6)
+ {
+ doc.write('<link rel="'+rel+'" href="'+href+'" charset="ISO-8859-1" type="text/css"/>');
+ }
+ else
+ {
+ var link = doc.createElement('link');
+
+ link.setAttribute('rel', rel);
+ link.setAttribute('href', href);
+ link.setAttribute('charset', 'ISO-8859-1');
+ link.setAttribute('type', 'text/css');
+
+ var head = doc.getElementsByTagName('head')[0];
+ head.appendChild(link);
+ }
+ },
+
+ /**
+ * Function: include
+ *
+ * Dynamically adds a script node to the document header.
+ *
+ * In production environments, the includes are resolved in the mxClient.js
+ * file to reduce the number of requests required for client startup. This
+ * function should only be used in development environments, but not in
+ * production systems.
+ */
+ include: function(src)
+ {
+ document.write('<script src="'+src+'"></script>');
+ },
+
+ /**
+ * Function: dispose
+ *
+ * Frees up memory in IE by resolving cyclic dependencies between the DOM
+ * and the JavaScript objects. This is always invoked in IE when the page
+ * unloads.
+ */
+ dispose: function()
+ {
+ // Cleans all objects where listeners have been added
+ for (var i = 0; i < mxEvent.objects.length; i++)
+ {
+ if (mxEvent.objects[i].mxListenerList != null)
+ {
+ mxEvent.removeAllListeners(mxEvent.objects[i]);
+ }
+ }
+ }
+
+};
+
+/**
+ * Variable: mxLoadResources
+ *
+ * Optional global config variable to toggle loading of the two resource files
+ * in <mxGraph> and <mxEditor>. Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * var mxLoadResources = false;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxLoadResources) == 'undefined')
+{
+ mxLoadResources = true;
+}
+
+/**
+ * Variable: mxLoadStylesheets
+ *
+ * Optional global config variable to toggle loading of the CSS files when
+ * the library is initialized. Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * var mxLoadStylesheets = false;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxLoadStylesheets) == 'undefined')
+{
+ mxLoadStylesheets = true;
+}
+
+/**
+ * Variable: basePath
+ *
+ * Basepath for all URLs in the core without trailing slash. Default is '.'.
+ * Set mxBasePath prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * mxBasePath = '/path/to/core/directory';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ *
+ * When using a relative path, the path is relative to the URL of the page that
+ * contains the assignment. Trailing slashes are automatically removed.
+ */
+if (typeof(mxBasePath) != 'undefined' && mxBasePath.length > 0)
+{
+ // Adds a trailing slash if required
+ if (mxBasePath.substring(mxBasePath.length - 1) == '/')
+ {
+ mxBasePath = mxBasePath.substring(0, mxBasePath.length - 1);
+ }
+
+ mxClient.basePath = mxBasePath;
+}
+else
+{
+ mxClient.basePath = '.';
+}
+
+/**
+ * Variable: imageBasePath
+ *
+ * Basepath for all images URLs in the core without trailing slash. Default is
+ * <mxClient.basePath> + '/images'. Set mxImageBasePath prior to loading the
+ * mxClient library as follows to override this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * mxImageBasePath = '/path/to/image/directory';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ *
+ * When using a relative path, the path is relative to the URL of the page that
+ * contains the assignment. Trailing slashes are automatically removed.
+ */
+if (typeof(mxImageBasePath) != 'undefined' && mxImageBasePath.length > 0)
+{
+ // Adds a trailing slash if required
+ if (mxImageBasePath.substring(mxImageBasePath.length - 1) == '/')
+ {
+ mxImageBasePath = mxImageBasePath.substring(0, mxImageBasePath.length - 1);
+ }
+
+ mxClient.imageBasePath = mxImageBasePath;
+}
+else
+{
+ mxClient.imageBasePath = mxClient.basePath + '/images';
+}
+
+/**
+ * Variable: language
+ *
+ * Defines the language of the client, eg. en for english, de for german etc.
+ * The special value 'none' will disable all built-in internationalization and
+ * resource loading. See <mxResources.getSpecialBundle> for handling identifiers
+ * with and without a dash.
+ *
+ * Set mxLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * mxLanguage = 'en';
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ *
+ * If internationalization is disabled, then the following variables should be
+ * overridden to reflect the current language of the system. These variables are
+ * cleared when i18n is disabled.
+ * <mxEditor.askZoomResource>, <mxEditor.lastSavedResource>,
+ * <mxEditor.currentFileResource>, <mxEditor.propertiesResource>,
+ * <mxEditor.tasksResource>, <mxEditor.helpResource>, <mxEditor.outlineResource>,
+ * <mxElbowEdgeHandler.doubleClickOrientationResource>, <mxUtils.errorResource>,
+ * <mxUtils.closeResource>, <mxGraphSelectionModel.doneResource>,
+ * <mxGraphSelectionModel.updatingSelectionResource>, <mxGraphView.doneResource>,
+ * <mxGraphView.updatingDocumentResource>, <mxCellRenderer.collapseExpandResource>,
+ * <mxGraph.containsValidationErrorsResource> and
+ * <mxGraph.alreadyConnectedResource>.
+ */
+if (typeof(mxLanguage) != 'undefined')
+{
+ mxClient.language = mxLanguage;
+}
+else
+{
+ mxClient.language = (mxClient.IS_IE) ? navigator.userLanguage : navigator.language;
+}
+
+/**
+ * Variable: defaultLanguage
+ *
+ * Defines the default language which is used in the common resource files. Any
+ * resources for this language will only load the common resource file, but not
+ * the language-specific resource file. Default is 'en'.
+ *
+ * Set mxDefaultLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * mxDefaultLanguage = 'de';
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxDefaultLanguage) != 'undefined')
+{
+ mxClient.defaultLanguage = mxDefaultLanguage;
+}
+else
+{
+ mxClient.defaultLanguage = 'en';
+}
+
+// Adds all required stylesheets and namespaces
+if (mxLoadStylesheets)
+{
+ mxClient.link('stylesheet', mxClient.basePath + '/css/common.css');
+}
+
+/**
+ * Variable: languages
+ *
+ * Defines the optional array of all supported language extensions. The default
+ * language does not have to be part of this list. See
+ * <mxResources.isLanguageSupported>.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * mxLanguages = ['de', 'it', 'fr'];
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ *
+ * This is used to avoid unnecessary requests to language files, ie. if a 404
+ * will be returned.
+ */
+if (typeof(mxLanguages) != 'undefined')
+{
+ mxClient.languages = mxLanguages;
+}
+
+if (mxClient.IS_IE)
+{
+ // IE9/10 standards mode uses SVG (VML is broken)
+ if (document.documentMode >= 9)
+ {
+ mxClient.IS_VML = false;
+ mxClient.IS_SVG = true;
+ }
+ 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. The use of setAttribute
+ // is not possible. See mxShape.init for more code to handle this specific document mode.
+ if (document.documentMode == 8)
+ {
+ document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
+ document.namespaces.add('o', 'urn:schemas-microsoft-com:office:office', '#default#VML');
+ }
+ else
+ {
+ document.namespaces.add('v', 'urn:schemas-microsoft-com:vml');
+ document.namespaces.add('o', 'urn:schemas-microsoft-com:office:office');
+ }
+
+ var ss = document.createStyleSheet();
+ ss.cssText = 'v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}';
+
+ if (mxLoadStylesheets)
+ {
+ mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css');
+ }
+ }
+
+ // Cleans up resources when the application terminates
+ window.attachEvent('onunload', mxClient.dispose);
+}
+
+mxClient.include(mxClient.basePath+'/js/util/mxLog.js');
+mxClient.include(mxClient.basePath+'/js/util/mxObjectIdentity.js');
+mxClient.include(mxClient.basePath+'/js/util/mxDictionary.js');
+mxClient.include(mxClient.basePath+'/js/util/mxResources.js');
+mxClient.include(mxClient.basePath+'/js/util/mxPoint.js');
+mxClient.include(mxClient.basePath+'/js/util/mxRectangle.js');
+mxClient.include(mxClient.basePath+'/js/util/mxEffects.js');
+mxClient.include(mxClient.basePath+'/js/util/mxUtils.js');
+mxClient.include(mxClient.basePath+'/js/util/mxConstants.js');
+mxClient.include(mxClient.basePath+'/js/util/mxEventObject.js');
+mxClient.include(mxClient.basePath+'/js/util/mxMouseEvent.js');
+mxClient.include(mxClient.basePath+'/js/util/mxEventSource.js');
+mxClient.include(mxClient.basePath+'/js/util/mxEvent.js');
+mxClient.include(mxClient.basePath+'/js/util/mxXmlRequest.js');
+mxClient.include(mxClient.basePath+'/js/util/mxClipboard.js');
+mxClient.include(mxClient.basePath+'/js/util/mxWindow.js');
+mxClient.include(mxClient.basePath+'/js/util/mxForm.js');
+mxClient.include(mxClient.basePath+'/js/util/mxImage.js');
+mxClient.include(mxClient.basePath+'/js/util/mxDivResizer.js');
+mxClient.include(mxClient.basePath+'/js/util/mxDragSource.js');
+mxClient.include(mxClient.basePath+'/js/util/mxToolbar.js');
+mxClient.include(mxClient.basePath+'/js/util/mxSession.js');
+mxClient.include(mxClient.basePath+'/js/util/mxUndoableEdit.js');
+mxClient.include(mxClient.basePath+'/js/util/mxUndoManager.js');
+mxClient.include(mxClient.basePath+'/js/util/mxUrlConverter.js');
+mxClient.include(mxClient.basePath+'/js/util/mxPanningManager.js');
+mxClient.include(mxClient.basePath+'/js/util/mxPath.js');
+mxClient.include(mxClient.basePath+'/js/util/mxPopupMenu.js');
+mxClient.include(mxClient.basePath+'/js/util/mxAutoSaveManager.js');
+mxClient.include(mxClient.basePath+'/js/util/mxAnimation.js');
+mxClient.include(mxClient.basePath+'/js/util/mxMorphing.js');
+mxClient.include(mxClient.basePath+'/js/util/mxImageBundle.js');
+mxClient.include(mxClient.basePath+'/js/util/mxImageExport.js');
+mxClient.include(mxClient.basePath+'/js/util/mxXmlCanvas2D.js');
+mxClient.include(mxClient.basePath+'/js/util/mxSvgCanvas2D.js');
+mxClient.include(mxClient.basePath+'/js/util/mxGuide.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxShape.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxStencil.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxStencilRegistry.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxStencilShape.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxMarker.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxActor.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxCloud.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxRectangleShape.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxEllipse.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxDoubleEllipse.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxRhombus.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxPolyline.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxArrow.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxText.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxTriangle.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxHexagon.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxLine.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxImageShape.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxLabel.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxCylinder.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxConnector.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxSwimlane.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxGraphLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxStackLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxPartitionLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxCompactTreeLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxFastOrganicLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxCircleLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxParallelEdgeLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxCompositeLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxEdgeLabelLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyNode.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyEdge.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyModel.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxMinimumCycleRemover.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxCoordinateAssignment.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/mxHierarchicalLayout.js');
+mxClient.include(mxClient.basePath+'/js/model/mxGraphModel.js');
+mxClient.include(mxClient.basePath+'/js/model/mxCell.js');
+mxClient.include(mxClient.basePath+'/js/model/mxGeometry.js');
+mxClient.include(mxClient.basePath+'/js/model/mxCellPath.js');
+mxClient.include(mxClient.basePath+'/js/view/mxPerimeter.js');
+mxClient.include(mxClient.basePath+'/js/view/mxPrintPreview.js');
+mxClient.include(mxClient.basePath+'/js/view/mxStylesheet.js');
+mxClient.include(mxClient.basePath+'/js/view/mxCellState.js');
+mxClient.include(mxClient.basePath+'/js/view/mxGraphSelectionModel.js');
+mxClient.include(mxClient.basePath+'/js/view/mxCellEditor.js');
+mxClient.include(mxClient.basePath+'/js/view/mxCellRenderer.js');
+mxClient.include(mxClient.basePath+'/js/view/mxEdgeStyle.js');
+mxClient.include(mxClient.basePath+'/js/view/mxStyleRegistry.js');
+mxClient.include(mxClient.basePath+'/js/view/mxGraphView.js');
+mxClient.include(mxClient.basePath+'/js/view/mxGraph.js');
+mxClient.include(mxClient.basePath+'/js/view/mxCellOverlay.js');
+mxClient.include(mxClient.basePath+'/js/view/mxOutline.js');
+mxClient.include(mxClient.basePath+'/js/view/mxMultiplicity.js');
+mxClient.include(mxClient.basePath+'/js/view/mxLayoutManager.js');
+mxClient.include(mxClient.basePath+'/js/view/mxSpaceManager.js');
+mxClient.include(mxClient.basePath+'/js/view/mxSwimlaneManager.js');
+mxClient.include(mxClient.basePath+'/js/view/mxTemporaryCellStates.js');
+mxClient.include(mxClient.basePath+'/js/view/mxCellStatePreview.js');
+mxClient.include(mxClient.basePath+'/js/view/mxConnectionConstraint.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxGraphHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxPanningHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxCellMarker.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxSelectionCellsHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxConnectionHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxConstraintHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxRubberband.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxVertexHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxEdgeHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxElbowEdgeHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxEdgeSegmentHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxKeyHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxTooltipHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxCellTracker.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxCellHighlight.js');
+mxClient.include(mxClient.basePath+'/js/editor/mxDefaultKeyHandler.js');
+mxClient.include(mxClient.basePath+'/js/editor/mxDefaultPopupMenu.js');
+mxClient.include(mxClient.basePath+'/js/editor/mxDefaultToolbar.js');
+mxClient.include(mxClient.basePath+'/js/editor/mxEditor.js');
+mxClient.include(mxClient.basePath+'/js/io/mxCodecRegistry.js');
+mxClient.include(mxClient.basePath+'/js/io/mxCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxObjectCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxCellCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxModelCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxRootChangeCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxChildChangeCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxTerminalChangeCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxGenericChangeCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxGraphCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxGraphViewCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxStylesheetCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxDefaultKeyHandlerCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxDefaultToolbarCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxDefaultPopupMenuCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxEditorCodec.js');
diff --git a/src/js/shape/mxActor.js b/src/js/shape/mxActor.js
new file mode 100644
index 0000000..e6a0765
--- /dev/null
+++ b/src/js/shape/mxActor.js
@@ -0,0 +1,183 @@
+/**
+ * $Id: mxActor.js,v 1.35 2012-07-31 11:46:53 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxActor
+ *
+ * Extends <mxShape> to implement an actor shape. If a custom shape with one
+ * filled area is needed, then this shape's <redrawPath> should be overridden.
+ *
+ * Example:
+ *
+ * (code)
+ * function SampleShape() { }
+ *
+ * SampleShape.prototype = new mxActor();
+ * SampleShape.prototype.constructor = vsAseShape;
+ *
+ * mxCellRenderer.prototype.defaultShapes['sample'] = SampleShape;
+ * SampleShape.prototype.redrawPath = function(path, x, y, w, h)
+ * {
+ * path.moveTo(0, 0);
+ * path.lineTo(w, h);
+ * // ...
+ * path.close();
+ * }
+ * (end)
+ *
+ * This shape is registered under <mxConstants.SHAPE_ACTOR> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxActor
+ *
+ * Constructs a new actor shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxActor(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxActor.prototype = new mxShape();
+mxActor.prototype.constructor = mxActor;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxActor.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxActor.prototype.preferModeHtml = false;
+
+/**
+ * Variable: vmlScale
+ *
+ * Renders VML with a scale of 2.
+ */
+mxActor.prototype.vmlScale = 2;
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node(s) to represent this shape.
+ */
+mxActor.prototype.createVml = function()
+{
+ var node = document.createElement('v:shape');
+ node.style.position = 'absolute';
+ this.configureVmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxActor.prototype.redrawVml = function()
+{
+ this.updateVmlShape(this.node);
+ this.node.path = this.createPath();
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxActor.prototype.createSvg = function()
+{
+ return this.createSvgGroup('path');
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxActor.prototype.redrawSvg = function()
+{
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ this.innerNode.setAttribute('stroke-width', strokeWidth);
+ this.innerNode.setAttribute('stroke-linejoin', 'round');
+
+ if (this.crisp && (this.rotation == null || this.rotation == 0))
+ {
+ this.innerNode.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.innerNode.removeAttribute('shape-rendering');
+ }
+
+ var d = this.createPath();
+
+ if (d.length > 0)
+ {
+ this.innerNode.setAttribute('d', d);
+
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform() +
+ (this.innerNode.getAttribute('transform') || ''));
+ this.shadowNode.setAttribute('stroke-width', strokeWidth);
+ this.shadowNode.setAttribute('d', d);
+ }
+ }
+ else
+ {
+ this.innerNode.removeAttribute('d');
+
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.removeAttribute('d');
+ }
+ }
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ this.innerNode.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxActor.prototype.redrawPath = function(path, x, y, w, h)
+{
+ var width = w/3;
+ path.moveTo(0, h);
+ path.curveTo(0, 3 * h / 5, 0, 2 * h / 5, w / 2, 2 * h / 5);
+ path.curveTo(w / 2 - width, 2 * h / 5, w / 2 - width, 0, w / 2, 0);
+ path.curveTo(w / 2 + width, 0, w / 2 + width, 2 * h / 5, w / 2, 2 * h / 5);
+ path.curveTo(w, 2 * h / 5, w, 3 * h / 5, w, h);
+ path.close();
+};
diff --git a/src/js/shape/mxArrow.js b/src/js/shape/mxArrow.js
new file mode 100644
index 0000000..93777d8
--- /dev/null
+++ b/src/js/shape/mxArrow.js
@@ -0,0 +1,226 @@
+/**
+ * $Id: mxArrow.js,v 1.31 2012-05-23 19:09:22 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxArrow
+ *
+ * Extends <mxShape> to implement an arrow shape. (The shape
+ * is used to represent edges, not vertices.)
+ * This shape is registered under <mxConstants.SHAPE_ARROW>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxArrow
+ *
+ * Constructs a new arrow shape.
+ *
+ * Parameters:
+ *
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ * arrowWidth - Optional integer that defines the arrow width. Default is
+ * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
+ * spacing - Optional integer that defines the spacing between the arrow shape
+ * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
+ * <spacing>.
+ * endSize - Optional integer that defines the size of the arrowhead. Default
+ * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
+ */
+function mxArrow(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)
+{
+ this.points = points;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+ this.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;
+ this.spacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;
+ this.endSize = (endSize != null) ? endSize : mxConstants.ARROW_SIZE;
+};
+
+/**
+ * Extends <mxActor>.
+ */
+mxArrow.prototype = new mxActor();
+mxArrow.prototype.constructor = mxArrow;
+
+/**
+ * Variable: addPipe
+ *
+ * Specifies if a SVG path should be created around any path to increase the
+ * tolerance for mouse events. Default is false since this shape is filled.
+ */
+mxArrow.prototype.addPipe = false;
+
+/**
+ * Variable: enableFill
+ *
+ * Specifies if fill colors should be ignored. This must be set to true for
+ * shapes that are stroked only. Default is true since this shape is filled.
+ */
+mxArrow.prototype.enableFill = true;
+
+/**
+ * Function: configureTransparentBackground
+ *
+ * Overidden to remove transparent background.
+ */
+mxArrow.prototype.configureTransparentBackground = function(node)
+{
+ // do nothing
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape.
+ */
+mxArrow.prototype.augmentBoundingBox = function(bbox)
+{
+ // FIXME: Fix precision, share math and cache results with painting code
+ bbox.grow(Math.max(this.arrowWidth / 2, this.endSize / 2) * this.scale);
+
+ mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+};
+
+/**
+ * Function: createVml
+ *
+ * Extends <mxShape.createVml> to ignore fill if <enableFill> is false.
+ */
+mxArrow.prototype.createVml = function()
+{
+ if (!this.enableFill)
+ {
+ this.fill = null;
+ }
+
+ return mxActor.prototype.createVml.apply(this, arguments);
+};
+
+/**
+ * Function: createSvg
+ *
+ * Extends <mxActor.createSvg> to ignore fill if <enableFill> is false and
+ * create an event handling shape if <this.addPipe> is true.
+ */
+mxArrow.prototype.createSvg = function()
+{
+ if (!this.enableFill)
+ {
+ this.fill = null;
+ }
+
+ var g = mxActor.prototype.createSvg.apply(this, arguments);
+
+ // Creates an invisible shape around the path for easier
+ // selection with the mouse. Note: Firefox does not ignore
+ // the value of the stroke attribute for pointer-events: stroke,
+ // it does, however, ignore the visibility attribute.
+ if (this.addPipe)
+ {
+ this.pipe = this.createSvgPipe();
+ g.appendChild(this.pipe);
+ }
+
+ return g;
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Extends <mxActor.reconfigure> to ignore fill if <enableFill> is false.
+ */
+mxArrow.prototype.reconfigure = function()
+{
+ if (!this.enableFill)
+ {
+ this.fill = null;
+ }
+
+ mxActor.prototype.reconfigure.apply(this, arguments);
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Extends <mxActor.redrawSvg> to update the event handling shape if one
+ * exists.
+ */
+mxArrow.prototype.redrawSvg = function()
+{
+ mxActor.prototype.redrawSvg.apply(this, arguments);
+
+ if (this.pipe != null)
+ {
+ var d = this.innerNode.getAttribute('d');
+
+ if (d != null)
+ {
+ this.pipe.setAttribute('d', this.innerNode.getAttribute('d'));
+ var strokeWidth = Math.round(this.strokewidth * this.scale);
+ this.pipe.setAttribute('stroke-width', strokeWidth + mxShape.prototype.SVG_STROKE_TOLERANCE);
+ }
+ }
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxArrow.prototype.redrawPath = function(path, x, y, w, h)
+{
+ // All points are offset
+ path.translate.x -= x;
+ path.translate.y -= y;
+
+ // Geometry of arrow
+ var spacing = this.spacing * this.scale;
+ var width = this.arrowWidth * this.scale;
+ var arrow = this.endSize * this.scale;
+
+ // Base vector (between end points)
+ var p0 = this.points[0];
+ var pe = this.points[this.points.length - 1];
+
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ var length = dist - 2 * spacing - arrow;
+
+ // Computes the norm and the inverse norm
+ var nx = dx / dist;
+ var ny = dy / dist;
+ var basex = length * nx;
+ var basey = length * ny;
+ var floorx = width * ny/3;
+ var floory = -width * nx/3;
+
+ // Computes points
+ var p0x = p0.x - floorx / 2 + spacing * nx;
+ var p0y = p0.y - floory / 2 + spacing * ny;
+ var p1x = p0x + floorx;
+ var p1y = p0y + floory;
+ var p2x = p1x + basex;
+ var p2y = p1y + basey;
+ var p3x = p2x + floorx;
+ var p3y = p2y + floory;
+ // p4 not necessary
+ var p5x = p3x - 3 * floorx;
+ var p5y = p3y - 3 * floory;
+
+ path.moveTo(p0x, p0y);
+ path.lineTo(p1x, p1y);
+ path.lineTo(p2x, p2y);
+ path.lineTo(p3x, p3y);
+ path.lineTo(pe.x - spacing * nx, pe.y - spacing * ny);
+ path.lineTo(p5x, p5y);
+ path.lineTo(p5x + floorx, p5y + floory);
+ path.lineTo(p0x, p0y);
+ path.close();
+};
diff --git a/src/js/shape/mxCloud.js b/src/js/shape/mxCloud.js
new file mode 100644
index 0000000..3893a1b
--- /dev/null
+++ b/src/js/shape/mxCloud.js
@@ -0,0 +1,56 @@
+/**
+ * $Id: mxCloud.js,v 1.12 2011-06-24 11:27:30 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCloud
+ *
+ * Extends <mxActor> to implement a cloud shape.
+ *
+ * This shape is registered under <mxConstants.SHAPE_CLOUD> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxCloud
+ *
+ * Constructs a new cloud shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxCloud(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxActor.
+ */
+mxCloud.prototype = new mxActor();
+mxCloud.prototype.constructor = mxActor;
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxCloud.prototype.redrawPath = function(path, x, y, w, h)
+{
+ path.moveTo(0.25 * w, 0.25 * h);
+ path.curveTo(0.05 * w, 0.25 * h, 0, 0.5 * h, 0.16 * w, 0.55 * h);
+ path.curveTo(0, 0.66 * h, 0.18 * w, 0.9 * h, 0.31 * w, 0.8 * h);
+ path.curveTo(0.4 * w, h, 0.7 * w, h, 0.8 * w, 0.8 * h);
+ path.curveTo(w, 0.8 * h, w, 0.6 * h, 0.875 * w, 0.5 * h);
+ path.curveTo(w, 0.3 * h, 0.8 * w, 0.1 * h, 0.625 * w, 0.2 * h);
+ path.curveTo(0.5 * w, 0.05 * h, 0.3 * w, 0.05 * h, 0.25 * w, 0.25 * h);
+ path.close();
+};
diff --git a/src/js/shape/mxConnector.js b/src/js/shape/mxConnector.js
new file mode 100644
index 0000000..092bf79
--- /dev/null
+++ b/src/js/shape/mxConnector.js
@@ -0,0 +1,446 @@
+/**
+ * $Id: mxConnector.js,v 1.80 2012-05-24 12:00:45 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxConnector
+ *
+ * Extends <mxShape> to implement a connector shape. The connector
+ * shape allows for arrow heads on either side.
+ *
+ * This shape is registered under <mxConstants.SHAPE_CONNECTOR> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxConnector
+ *
+ * Constructs a new connector shape.
+ *
+ * Parameters:
+ *
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * Default is 'black'.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxConnector(points, stroke, strokewidth)
+{
+ this.points = points;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxConnector.prototype = new mxShape();
+mxConnector.prototype.constructor = mxConnector;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxConnector.prototype.vmlNodes = mxConnector.prototype.vmlNodes.concat([
+ 'shapeNode', 'start', 'end', 'startStroke', 'endStroke', 'startFill', 'endFill']);
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxConnector.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxConnector.prototype.preferModeHtml = false;
+
+/**
+ * Variable: allowCrispMarkers
+ *
+ * Specifies if <mxShape.crisp> should be allowed for markers. Default is false.
+ */
+mxConnector.prototype.allowCrispMarkers = false;
+
+/**
+ * Variable: addPipe
+ *
+ * Specifies if a SVG path should be created around any path to increase the
+ * tolerance for mouse events. Default is false since this shape is filled.
+ */
+mxConnector.prototype.addPipe = true;
+
+/**
+ * Function: configureHtmlShape
+ *
+ * Overrides <mxShape.configureHtmlShape> to clear the border and background.
+ */
+mxConnector.prototype.configureHtmlShape = function(node)
+{
+ mxShape.prototype.configureHtmlShape.apply(this, arguments);
+ node.style.borderStyle = '';
+ node.style.background = '';
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxConnector.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+ node.style.position = 'absolute';
+ this.shapeNode = document.createElement('v:shape');
+ this.updateVmlStrokeColor(this.shapeNode);
+ this.updateVmlStrokeNode(this.shapeNode);
+ node.appendChild(this.shapeNode);
+ this.shapeNode.filled = 'false';
+
+ if (this.isShadow)
+ {
+ this.createVmlShadow(this.shapeNode);
+ }
+
+ // Creates the start arrow as an additional child path
+ if (this.startArrow != null)
+ {
+ this.start = document.createElement('v:shape');
+ this.start.style.position = 'absolute';
+
+ // Only required for opacity and joinstyle
+ this.startStroke = document.createElement('v:stroke');
+ this.startStroke.joinstyle = 'miter';
+ this.start.appendChild(this.startStroke);
+
+ this.startFill = document.createElement('v:fill');
+ this.start.appendChild(this.startFill);
+
+ node.appendChild(this.start);
+ }
+
+ // Creates the end arrows as an additional child path
+ if (this.endArrow != null)
+ {
+ this.end = document.createElement('v:shape');
+ this.end.style.position = 'absolute';
+
+ // Only required for opacity and joinstyle
+ this.endStroke = document.createElement('v:stroke');
+ this.endStroke.joinstyle = 'miter';
+ this.end.appendChild(this.endStroke);
+
+ this.endFill = document.createElement('v:fill');
+ this.end.appendChild(this.endFill);
+
+ node.appendChild(this.end);
+ }
+
+ this.updateVmlMarkerOpacity();
+
+ return node;
+};
+
+/**
+ * Function: updateVmlMarkerOpacity
+ *
+ * Updates the opacity for the markers in VML.
+ */
+mxConnector.prototype.updateVmlMarkerOpacity = function()
+{
+ var op = (this.opacity != null) ? (this.opacity + '%') : '100%';
+
+ if (this.start != null)
+ {
+ this.startFill.opacity = op;
+ this.startStroke.opacity = op;
+ }
+
+ if (this.end != null)
+ {
+ this.endFill.opacity = op;
+ this.endStroke.opacity = op;
+ }
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Redraws this VML shape by invoking <updateVmlShape> on this.node.
+ */
+mxConnector.prototype.reconfigure = function()
+{
+ // Never fill a connector
+ this.fill = null;
+
+ if (mxUtils.isVml(this.node))
+ {
+ // Updates the style of the given shape
+ // LATER: Check if this can be replaced with redrawVml and
+ // updating the color, dash pattern and shadow.
+ this.node.style.visibility = 'hidden';
+ this.configureVmlShape(this.shapeNode);
+ this.updateVmlMarkerOpacity();
+ this.node.style.visibility = 'visible';
+ }
+ else
+ {
+ mxShape.prototype.reconfigure.apply(this, arguments);
+ }
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Redraws this VML shape by invoking <updateVmlShape> on this.node.
+ */
+mxConnector.prototype.redrawVml = function()
+{
+ if (this.node != null && this.points != null && this.bounds != null &&
+ !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+ !isNaN(this.bounds.width) && !isNaN(this.bounds.height))
+ {
+ var w = Math.max(0, Math.round(this.bounds.width));
+ var h = Math.max(0, Math.round(this.bounds.height));
+ var cs = w + ',' + h;
+ w += 'px';
+ h += 'px';
+
+ // Computes the marker paths before the main path is updated so
+ // that offsets can be taken into account
+ if (this.start != null)
+ {
+ this.start.style.width = w;
+ this.start.style.height = h;
+ this.start.coordsize = cs;
+
+ var p0 = this.points[1];
+ var pe = this.points[0];
+
+ var size = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE);
+ this.startOffset = this.redrawMarker(this.start, this.startArrow, p0, pe, this.stroke, size);
+ }
+
+ if (this.end != null)
+ {
+ this.end.style.width = w;
+ this.end.style.height = h;
+ this.end.coordsize = cs;
+
+ var n = this.points.length;
+ var p0 = this.points[n - 2];
+ var pe = this.points[n - 1];
+
+ var size = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);
+ this.endOffset = this.redrawMarker(this.end, this.endArrow, p0, pe, this.stroke, size);
+ }
+
+ this.updateVmlShape(this.node);
+ this.updateVmlShape(this.shapeNode);
+ this.shapeNode.filled = 'false';
+
+ // Adds custom dash pattern
+ if (this.isDashed)
+ {
+ var pat = mxUtils.getValue(this.style, 'dashStyle', null);
+
+ if (pat != null)
+ {
+ this.strokeNode.dashstyle = pat;
+ }
+
+ if (this.shadowStrokeNode != null)
+ {
+ this.shadowStrokeNode.dashstyle = this.strokeNode.dashstyle;
+ }
+ }
+ }
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node to represent this shape.
+ */
+mxConnector.prototype.createSvg = function()
+{
+ this.fill = null;
+ var g = this.createSvgGroup('path');
+
+ // Creates the start arrow as an additional child path
+ if (this.startArrow != null)
+ {
+ this.start = document.createElementNS(mxConstants.NS_SVG, 'path');
+ g.appendChild(this.start);
+ }
+
+ // Creates the end arrows as an additional child path
+ if (this.endArrow != null)
+ {
+ this.end = document.createElementNS(mxConstants.NS_SVG, 'path');
+ g.appendChild(this.end);
+ }
+
+ // Creates an invisible shape around the path for easier
+ // selection with the mouse. Note: Firefox does not ignore
+ // the value of the stroke attribute for pointer-events: stroke,
+ // it does, however, ignore the visibility attribute.
+ if (this.addPipe)
+ {
+ this.pipe = this.createSvgPipe();
+ g.appendChild(this.pipe);
+ }
+
+ return g;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxConnector.prototype.redrawSvg = function()
+{
+ // Computes the markers first which modifies the coordinates of the
+ // endpoints to not overlap with the painted marker then updates the actual
+ // shape for the edge to take the modified endpoints into account.
+ if (this.points != null && this.points[0] != null)
+ {
+ var color = this.innerNode.getAttribute('stroke');
+
+ // Draws the start marker
+ if (this.start != null)
+ {
+ var p0 = this.points[1];
+ var pe = this.points[0];
+
+ var size = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE,
+ mxConstants.DEFAULT_MARKERSIZE);
+ this.startOffset = this.redrawMarker(this.start,
+ this.startArrow, p0, pe, color, size);
+
+ if (this.allowCrispMarkers && this.crisp)
+ {
+ this.start.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.start.removeAttribute('shape-rendering');
+ }
+ }
+
+ // Draws the end marker
+ if (this.end != null)
+ {
+ var n = this.points.length;
+
+ var p0 = this.points[n - 2];
+ var pe = this.points[n - 1];
+
+ var size = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE,
+ mxConstants.DEFAULT_MARKERSIZE);
+ this.endOffset = this.redrawMarker(this.end,
+ this.endArrow, p0, pe, color, size);
+
+ if (this.allowCrispMarkers && this.crisp)
+ {
+ this.end.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.end.removeAttribute('shape-rendering');
+ }
+ }
+ }
+
+ this.updateSvgShape(this.innerNode);
+ var d = this.innerNode.getAttribute('d');
+
+ if (d != null)
+ {
+ var strokeWidth = Math.round(this.strokewidth * this.scale);
+
+ // Updates the tolerance of the invisible shape for event handling
+ if (this.pipe != null)
+ {
+ this.pipe.setAttribute('d', this.innerNode.getAttribute('d'));
+ this.pipe.setAttribute('stroke-width', strokeWidth + mxShape.prototype.SVG_STROKE_TOLERANCE);
+ }
+
+ // Updates the shadow
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform());
+ this.shadowNode.setAttribute('d', d);
+ this.shadowNode.setAttribute('stroke-width', strokeWidth);
+ }
+ }
+
+ // Adds custom dash pattern
+ if (this.isDashed)
+ {
+ var pat = this.createDashPattern(this.scale * this.strokewidth);
+
+ if (pat != null)
+ {
+ this.innerNode.setAttribute('stroke-dasharray', pat);
+ }
+ }
+
+ // Updates the shadow
+ if (this.shadowNode != null)
+ {
+ var pat = this.innerNode.getAttribute('stroke-dasharray');
+
+ if (pat != null)
+ {
+ this.shadowNode.setAttribute('stroke-dasharray', pat);
+ }
+ }
+};
+
+/**
+ * Function: createDashPattern
+ *
+ * Creates a dash pattern for the given factor.
+ */
+mxConnector.prototype.createDashPattern = function(factor)
+{
+ var value = mxUtils.getValue(this.style, 'dashPattern', null);
+
+ if (value != null)
+ {
+ var tmp = value.split(' ');
+ var pat = [];
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ if (tmp[i].length > 0)
+ {
+ pat.push(Math.round(Number(tmp[i]) * factor));
+ }
+ }
+
+ return pat.join(' ');
+ }
+
+ return null;
+};
+
+/**
+ * Function: redrawMarker
+ *
+ * Updates the given SVG or VML marker.
+ */
+mxConnector.prototype.redrawMarker = function(node, type, p0, pe, color, size)
+{
+ return mxMarker.paintMarker(node, type, p0, pe, color, this.strokewidth,
+ size, this.scale, this.bounds.x, this.bounds.y, this.start == node,
+ this.style);
+};
diff --git a/src/js/shape/mxCylinder.js b/src/js/shape/mxCylinder.js
new file mode 100644
index 0000000..9a45760
--- /dev/null
+++ b/src/js/shape/mxCylinder.js
@@ -0,0 +1,319 @@
+/**
+ * $Id: mxCylinder.js,v 1.38 2012-07-31 11:46:53 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCylinder
+ *
+ * Extends <mxShape> to implement an cylinder shape. If a
+ * custom shape with one filled area and an overlay path is
+ * needed, then this shape's <redrawPath> should be overridden.
+ * This shape is registered under <mxConstants.SHAPE_CYLINDER>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxCylinder
+ *
+ * Constructs a new cylinder shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxCylinder(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxCylinder.prototype = new mxShape();
+mxCylinder.prototype.constructor = mxCylinder;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxCylinder.prototype.vmlNodes = mxCylinder.prototype.vmlNodes.concat(['background', 'foreground']);
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxCylinder.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxCylinder.prototype.preferModeHtml = false;
+
+/**
+ * Variable: addPipe
+ *
+ * Specifies if a SVG path should be created around the background for better
+ * hit detection. Default is false.
+ */
+mxCylinder.prototype.addPipe = false;
+
+/**
+ * Variable: strokedBackground
+ *
+ * Specifies if the background should be stroked. Default is true.
+ */
+mxCylinder.prototype.strokedBackground = true;
+
+/**
+ * Variable: maxHeight
+ *
+ * Defines the maximum height of the top and bottom part
+ * of the cylinder shape.
+ */
+mxCylinder.prototype.maxHeight = 40;
+
+/**
+ * Variable: vmlScale
+ *
+ * Renders VML with a scale of 2.
+ */
+mxCylinder.prototype.vmlScale = 2;
+
+/**
+ * Function: create
+ *
+ * Overrides the method to make sure the <stroke> is never
+ * null. If it is null is will be assigned the <fill> color.
+ */
+mxCylinder.prototype.create = function(container)
+{
+ if (this.stroke == null)
+ {
+ this.stroke = this.fill;
+ }
+
+ // Calls superclass implementation of create
+ return mxShape.prototype.create.apply(this, arguments);
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Overrides the method to make sure the <stroke> is applied to the foreground.
+ */
+mxCylinder.prototype.reconfigure = function()
+{
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.configureSvgShape(this.foreground);
+ this.foreground.setAttribute('fill', 'none');
+ }
+ else if (mxUtils.isVml(this.node))
+ {
+ this.configureVmlShape(this.background);
+ this.configureVmlShape(this.foreground);
+ }
+
+ mxShape.prototype.reconfigure.apply(this);
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxCylinder.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+
+ // Draws the background
+ this.background = document.createElement('v:shape');
+ this.label = this.background;
+ this.configureVmlShape(this.background);
+ node.appendChild(this.background);
+
+ // Ignores values that only apply to the background
+ this.fill = null;
+ this.isShadow = false;
+ this.configureVmlShape(node);
+
+ // Draws the foreground
+ this.foreground = document.createElement('v:shape');
+ this.configureVmlShape(this.foreground);
+
+ // To match SVG defaults jointsyle miter, miterlimit 4
+ this.fgStrokeNode = document.createElement('v:stroke');
+ this.fgStrokeNode.joinstyle = 'miter';
+ this.fgStrokeNode.miterlimit = 4;
+ this.foreground.appendChild(this.fgStrokeNode);
+
+ node.appendChild(this.foreground);
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxCylinder.prototype.redrawVml = function()
+{
+ this.updateVmlShape(this.node);
+ this.updateVmlShape(this.background);
+ this.updateVmlShape(this.foreground);
+ this.background.path = this.createPath(false);
+ this.foreground.path = this.createPath(true);
+
+ this.fgStrokeNode.dashstyle = this.strokeNode.dashstyle;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxCylinder.prototype.createSvg = function()
+{
+ var g = this.createSvgGroup('path');
+ this.foreground = document.createElementNS(mxConstants.NS_SVG, 'path');
+
+ if (this.stroke != null && this.stroke != mxConstants.NONE)
+ {
+ this.foreground.setAttribute('stroke', this.stroke);
+ }
+ else
+ {
+ this.foreground.setAttribute('stroke', 'none');
+ }
+
+ this.foreground.setAttribute('fill', 'none');
+ g.appendChild(this.foreground);
+
+ if (this.addPipe)
+ {
+ this.pipe = this.createSvgPipe();
+ g.appendChild(this.pipe);
+ }
+
+ return g;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxCylinder.prototype.redrawSvg = function()
+{
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ this.innerNode.setAttribute('stroke-width', strokeWidth);
+
+ if (this.crisp && (this.rotation == null || this.rotation == 0))
+ {
+ this.innerNode.setAttribute('shape-rendering', 'crispEdges');
+ this.foreground.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.innerNode.removeAttribute('shape-rendering');
+ this.foreground.removeAttribute('shape-rendering');
+ }
+
+ // Paints background
+ var d = this.createPath(false);
+
+ if (d.length > 0)
+ {
+ this.innerNode.setAttribute('d', d);
+
+ // Updates event handling element
+ if (this.pipe != null)
+ {
+ this.pipe.setAttribute('d', d);
+ this.pipe.setAttribute('stroke-width', strokeWidth + mxShape.prototype.SVG_STROKE_TOLERANCE);
+ this.pipe.setAttribute('transform', (this.innerNode.getAttribute('transform') || ''));
+ }
+ }
+ else
+ {
+ this.innerNode.removeAttribute('d');
+
+ // Updates event handling element
+ if (this.pipe != null)
+ {
+ this.pipe.removeAttribute('d');
+ }
+ }
+
+ // Stroked background
+ if (!this.strokedBackground)
+ {
+ this.innerNode.setAttribute('stroke', 'none');
+ }
+
+ // Paints shadow
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('stroke-width', strokeWidth);
+ this.shadowNode.setAttribute('d', d);
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform());
+ }
+
+ // Paints foreground
+ d = this.createPath(true);
+
+ if (d.length > 0)
+ {
+ this.foreground.setAttribute('stroke-width', strokeWidth);
+ this.foreground.setAttribute('d', d);
+ }
+ else
+ {
+ this.foreground.removeAttribute('d');
+ }
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ this.innerNode.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ this.foreground.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxCylinder.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+{
+ var dy = Math.min(this.maxHeight, Math.round(h / 5));
+
+ if (isForeground)
+ {
+ path.moveTo(0, dy);
+ path.curveTo(0, 2 * dy, w, 2 * dy, w, dy);
+ }
+ else
+ {
+ path.moveTo(0, dy);
+ path.curveTo(0, -dy / 3, w, -dy / 3, w, dy);
+ path.lineTo(w, h - dy);
+ path.curveTo(w, h + dy / 3, 0, h + dy / 3, 0, h - dy);
+ path.close();
+ }
+};
diff --git a/src/js/shape/mxDoubleEllipse.js b/src/js/shape/mxDoubleEllipse.js
new file mode 100644
index 0000000..7854851
--- /dev/null
+++ b/src/js/shape/mxDoubleEllipse.js
@@ -0,0 +1,203 @@
+/**
+ * $Id: mxDoubleEllipse.js,v 1.19 2012-05-21 18:27:17 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDoubleEllipse
+ *
+ * Extends <mxShape> to implement a double ellipse shape.
+ * This shape is registered under <mxConstants.SHAPE_DOUBLE_ELLIPSE>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxDoubleEllipse
+ *
+ * Constructs a new ellipse shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxDoubleEllipse(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxDoubleEllipse.prototype = new mxShape();
+mxDoubleEllipse.prototype.constructor = mxDoubleEllipse;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxDoubleEllipse.prototype.vmlNodes = mxDoubleEllipse.prototype.vmlNodes.concat(['background', 'foreground']);
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxDoubleEllipse.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxDoubleEllipse.prototype.preferModeHtml = false;
+
+/**
+ * Variable: vmlScale
+ *
+ * Renders VML with a scale of 2.
+ */
+mxDoubleEllipse.prototype.vmlScale = 2;
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxDoubleEllipse.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+
+ // Draws the background
+ this.background = document.createElement('v:arc');
+ this.background.startangle = '0';
+ this.background.endangle = '360';
+ this.configureVmlShape(this.background);
+
+ node.appendChild(this.background);
+
+ // Ignores values that only apply to the background
+ this.label = this.background;
+ this.isShadow = false;
+ this.fill = null;
+
+ // Draws the foreground
+ this.foreground = document.createElement('v:oval');
+ this.configureVmlShape(this.foreground);
+
+ node.appendChild(this.foreground);
+
+ this.stroke = null;
+ this.configureVmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxDoubleEllipse.prototype.redrawVml = function()
+{
+ this.updateVmlShape(this.node);
+ this.updateVmlShape(this.background);
+ this.updateVmlShape(this.foreground);
+
+ var inset = Math.round((this.strokewidth + 3) * this.scale) * this.vmlScale;
+ var w = Math.round(this.bounds.width * this.vmlScale);
+ var h = Math.round(this.bounds.height * this.vmlScale);
+
+ this.foreground.style.top = inset + 'px'; // relative
+ this.foreground.style.left = inset + 'px'; // relative
+ this.foreground.style.width = Math.max(0, w - 2 * inset) + 'px';
+ this.foreground.style.height = Math.max(0, h - 2 * inset) + 'px';
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxDoubleEllipse.prototype.createSvg = function()
+{
+ var g = this.createSvgGroup('ellipse');
+ this.foreground = document.createElementNS(mxConstants.NS_SVG, 'ellipse');
+
+ if (this.stroke != null)
+ {
+ this.foreground.setAttribute('stroke', this.stroke);
+ }
+ else
+ {
+ this.foreground.setAttribute('stroke', 'none');
+ }
+
+ this.foreground.setAttribute('fill', 'none');
+ g.appendChild(this.foreground);
+
+ return g;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxDoubleEllipse.prototype.redrawSvg = function()
+{
+ if (this.crisp)
+ {
+ this.innerNode.setAttribute('shape-rendering', 'crispEdges');
+ this.foreground.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.innerNode.removeAttribute('shape-rendering');
+ this.foreground.removeAttribute('shape-rendering');
+ }
+
+ this.updateSvgNode(this.innerNode);
+ this.updateSvgNode(this.shadowNode);
+ this.updateSvgNode(this.foreground, (this.strokewidth + 3) * this.scale);
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ this.innerNode.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+};
+
+/**
+ * Function: updateSvgNode
+ *
+ * Updates the given node to reflect the new <bounds> and <scale>.
+ */
+mxDoubleEllipse.prototype.updateSvgNode = function(node, inset)
+{
+ inset = (inset != null) ? inset : 0;
+
+ if (node != null)
+ {
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ node.setAttribute('stroke-width', strokeWidth);
+
+ node.setAttribute('cx', this.bounds.x + this.bounds.width / 2);
+ node.setAttribute('cy', this.bounds.y + this.bounds.height / 2);
+ node.setAttribute('rx', Math.max(0, this.bounds.width / 2 - inset));
+ node.setAttribute('ry', Math.max(0, this.bounds.height / 2 - inset));
+
+ // Updates the transform of the shadow
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform());
+ }
+ }
+};
diff --git a/src/js/shape/mxEllipse.js b/src/js/shape/mxEllipse.js
new file mode 100644
index 0000000..f3882cf
--- /dev/null
+++ b/src/js/shape/mxEllipse.js
@@ -0,0 +1,132 @@
+/**
+ * $Id: mxEllipse.js,v 1.20 2012-04-04 07:34:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEllipse
+ *
+ * Extends <mxShape> to implement an ellipse shape.
+ * This shape is registered under <mxConstants.SHAPE_ELLIPSE>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxEllipse
+ *
+ * Constructs a new ellipse shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxEllipse(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxEllipse.prototype = new mxShape();
+mxEllipse.prototype.constructor = mxEllipse;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxEllipse.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxEllipse.prototype.preferModeHtml = false;
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxEllipse.prototype.createVml = function()
+{
+ // Uses an arc not an oval to make sure the
+ // textbox fills out the outer bounds of the
+ // circle, not just the inner rectangle
+ var node = document.createElement('v:arc');
+ node.startangle = '0';
+ node.endangle = '360';
+ this.configureVmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxEllipse.prototype.createSvg = function()
+{
+ return this.createSvgGroup('ellipse');
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxEllipse.prototype.redrawSvg = function()
+{
+ if (this.crisp)
+ {
+ this.innerNode.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.innerNode.removeAttribute('shape-rendering');
+ }
+
+ this.updateSvgNode(this.innerNode);
+ this.updateSvgNode(this.shadowNode);
+};
+
+/**
+ * Function: updateSvgNode
+ *
+ * Updates the given node to reflect the new <bounds> and <scale>.
+ */
+mxEllipse.prototype.updateSvgNode = function(node)
+{
+ if (node != null)
+ {
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ node.setAttribute('stroke-width', strokeWidth);
+
+ node.setAttribute('cx', this.bounds.x + this.bounds.width / 2);
+ node.setAttribute('cy', this.bounds.y + this.bounds.height / 2);
+ node.setAttribute('rx', this.bounds.width / 2);
+ node.setAttribute('ry', this.bounds.height / 2);
+
+ // Updates the shadow offset
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform());
+ }
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ node.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+ }
+};
diff --git a/src/js/shape/mxHexagon.js b/src/js/shape/mxHexagon.js
new file mode 100644
index 0000000..7fa45a3
--- /dev/null
+++ b/src/js/shape/mxHexagon.js
@@ -0,0 +1,37 @@
+/**
+ * $Id: mxHexagon.js,v 1.8 2011-09-02 10:01:00 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxHexagon
+ *
+ * Implementation of the hexagon shape.
+ *
+ * Constructor: mxHexagon
+ *
+ * Constructs a new hexagon shape.
+ */
+function mxHexagon() { };
+
+/**
+ * Extends <mxActor>.
+ */
+mxHexagon.prototype = new mxActor();
+mxHexagon.prototype.constructor = mxHexagon;
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxHexagon.prototype.redrawPath = function(path, x, y, w, h)
+{
+ path.moveTo(0.25 * w, 0);
+ path.lineTo(0.75 * w, 0);
+ path.lineTo(w, 0.5 * h);
+ path.lineTo(0.75 * w, h);
+ path.lineTo(0.25 * w, h);
+ path.lineTo(0, 0.5 * h);
+ path.close();
+};
diff --git a/src/js/shape/mxImageShape.js b/src/js/shape/mxImageShape.js
new file mode 100644
index 0000000..2f1eab0
--- /dev/null
+++ b/src/js/shape/mxImageShape.js
@@ -0,0 +1,405 @@
+/**
+ * $Id: mxImageShape.js,v 1.67 2012-04-22 10:16:23 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxImageShape
+ *
+ * Extends <mxShape> to implement an image shape. This shape is registered
+ * under <mxConstants.SHAPE_IMAGE> in <mxCellRenderer>.
+ *
+ * Constructor: mxImageShape
+ *
+ * Constructs a new image shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * image - String that specifies the URL of the image. This is stored in
+ * <image>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 0. This is stored in <strokewidth>.
+ */
+function mxImageShape(bounds, image, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.image = (image != null) ? image : '';
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+ this.isShadow = false;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxImageShape.prototype = new mxShape();
+mxImageShape.prototype.constructor = mxImageShape;
+
+/**
+ * Variable: crisp
+ *
+ * Disables crisp rendering via attributes. Image quality defines the rendering
+ * quality. Default is false.
+ */
+mxImageShape.prototype.crisp = false;
+
+/**
+ * Variable: preserveImageAspect
+ *
+ * Switch to preserve image aspect. Default is true.
+ */
+mxImageShape.prototype.preserveImageAspect = true;
+
+/**
+ * Function: apply
+ *
+ * Overrides <mxShape.apply> to replace the fill and stroke colors with the
+ * respective values from <mxConstants.STYLE_IMAGE_BACKGROUND> and
+ * <mxConstants.STYLE_IMAGE_BORDER>.
+ *
+ * Applies the style of the given <mxCellState> to the shape. This
+ * implementation assigns the following styles to local fields:
+ *
+ * - <mxConstants.STYLE_IMAGE_BACKGROUND> => fill
+ * - <mxConstants.STYLE_IMAGE_BORDER> => stroke
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxImageShape.prototype.apply = function(state)
+{
+ mxShape.prototype.apply.apply(this, arguments);
+
+ this.fill = null;
+ this.stroke = null;
+
+ if (this.style != null)
+ {
+ this.fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND);
+ this.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER);
+ this.preserveImageAspect = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1;
+ this.gradient = null;
+ }
+};
+
+/**
+ * Function: create
+ *
+ * Override to create HTML regardless of gradient and
+ * rounded property.
+ */
+mxImageShape.prototype.create = function()
+{
+ var node = null;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ // Workaround: To avoid control-click on images in Firefox to
+ // open the image in a new window, this image needs to be placed
+ // inside a group with a rectangle in the foreground which has a
+ // fill property but no visibility and absorbs all events.
+ // The image in turn must have all pointer-events disabled.
+ node = this.createSvgGroup('rect');
+ this.innerNode.setAttribute('visibility', 'hidden');
+ this.innerNode.setAttribute('pointer-events', 'fill');
+
+ this.imageNode = document.createElementNS(mxConstants.NS_SVG, 'image');
+ this.imageNode.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', this.image);
+ this.imageNode.setAttribute('style', 'pointer-events:none');
+ this.configureSvgShape(this.imageNode);
+
+ // Removes invalid attributes on the image node
+ this.imageNode.removeAttribute('stroke');
+ this.imageNode.removeAttribute('fill');
+ node.insertBefore(this.imageNode, this.innerNode);
+
+ // Inserts node for background and border color rendering
+ if ((this.fill != null && this.fill != mxConstants.NONE) ||
+ (this.stroke != null && this.stroke != mxConstants.NONE))
+ {
+ this.bg = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ node.insertBefore(this.bg, node.firstChild);
+ }
+
+ // Preserves image aspect as default
+ if (!this.preserveImageAspect)
+ {
+ this.imageNode.setAttribute('preserveAspectRatio', 'none');
+ }
+ }
+ else
+ {
+ // Uses VML image for all non-embedded images in IE to support better
+ // image flipping quality and avoid workarounds for event redirection
+ var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPH, 0) == 1;
+ var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPV, 0) == 1;
+ var img = this.image.toUpperCase();
+
+ // Handles non-flipped embedded images in IE6
+ if (mxClient.IS_IE && !flipH && !flipV && img.substring(0, 6) == 'MHTML:')
+ {
+ // LATER: Check if outer DIV is required or if aspect can be implemented
+ // by adding an offset to the image loading or the background via CSS.
+ this.imageNode = document.createElement('DIV');
+ this.imageNode.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader ' +
+ '(src=\'' + this.image + '\', sizingMethod=\'scale\')';
+
+ node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+ node.appendChild(this.imageNode);
+ }
+ // Handles all data URL images and HTML images for IE9 with no VML support (in SVG mode)
+ else if (!mxClient.IS_IE || img.substring(0, 5) == 'DATA:' || document.documentMode >= 9)
+ {
+ this.imageNode = document.createElement('img');
+ this.imageNode.setAttribute('src', this.image);
+ this.imageNode.setAttribute('border', '0');
+ this.imageNode.style.position = 'absolute';
+ this.imageNode.style.width = '100%';
+ this.imageNode.style.height = '100%';
+
+ node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+ node.appendChild(this.imageNode);
+ }
+ else
+ {
+ this.imageNode = document.createElement('v:image');
+ this.imageNode.style.position = 'absolute';
+ this.imageNode.src = this.image;
+
+ // Needed to draw the background and border but known
+ // to cause problems in print preview with https
+ node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+
+ // Workaround for cropped images in IE7/8
+ node.style.overflow = 'visible';
+ node.appendChild(this.imageNode);
+ }
+ }
+
+ return node;
+};
+
+/**
+ * Function: updateAspect
+ *
+ * Updates the aspect of the image for the given image width and height.
+ */
+mxImageShape.prototype.updateAspect = function(w, h)
+{
+ var s = Math.min(this.bounds.width / w, this.bounds.height / h);
+ w = Math.max(0, Math.round(w * s));
+ h = Math.max(0, Math.round(h * s));
+ var x0 = Math.max(0, Math.round((this.bounds.width - w) / 2));
+ var y0 = Math.max(0, Math.round((this.bounds.height - h) / 2));
+ var st = this.imageNode.style;
+
+ // Positions the child node relative to the parent node
+ if (this.imageNode.parentNode == this.node)
+ {
+ // Workaround for duplicate offset in VML in IE8 is
+ // to use parent padding instead of left and top
+ this.node.style.paddingLeft = x0 + 'px';
+ this.node.style.paddingTop = y0 + 'px';
+ }
+ else
+ {
+ st.left = (Math.round(this.bounds.x) + x0) + 'px';
+ st.top = (Math.round(this.bounds.y) + y0) + 'px';
+ }
+
+ st.width = w + 'px';
+ st.height = h + 'px';
+};
+
+/**
+ * Function: scheduleUpdateAspect
+ *
+ * Schedules an asynchronous <updateAspect> using the current <image>.
+ */
+mxImageShape.prototype.scheduleUpdateAspect = function()
+{
+ var img = new Image();
+
+ img.onload = mxUtils.bind(this, function()
+ {
+ mxImageShape.prototype.updateAspect.call(this, img.width, img.height);
+ });
+
+ img.src = this.image;
+};
+
+/**
+ * Function: redraw
+ *
+ * Overrides <mxShape.redraw> to preserve the aspect ratio of images.
+ */
+mxImageShape.prototype.redraw = function()
+{
+ mxShape.prototype.redraw.apply(this, arguments);
+
+ if (this.imageNode != null && this.bounds != null)
+ {
+ // Horizontal and vertical flipping
+ var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPH, 0) == 1;
+ var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPV, 0) == 1;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var sx = 1;
+ var sy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ if (flipH)
+ {
+ sx = -1;
+ dx = -this.bounds.width - 2 * this.bounds.x;
+ }
+
+ if (flipV)
+ {
+ sy = -1;
+ dy = -this.bounds.height - 2 * this.bounds.y;
+ }
+
+ // Adds image tansformation to existing transforms
+ var transform = (this.imageNode.getAttribute('transform') || '') +
+ ' scale('+sx+' '+sy+')'+ ' translate('+dx+' '+dy+')';
+ this.imageNode.setAttribute('transform', transform);
+ }
+ else
+ {
+ // Sets default size (no aspect)
+ if (this.imageNode.nodeName != 'DIV')
+ {
+ this.imageNode.style.width = Math.max(0, Math.round(this.bounds.width)) + 'px';
+ this.imageNode.style.height = Math.max(0, Math.round(this.bounds.height)) + 'px';
+ }
+
+ // Preserves image aspect
+ if (this.preserveImageAspect)
+ {
+ this.scheduleUpdateAspect();
+ }
+
+ if (flipH || flipV)
+ {
+ if (mxUtils.isVml(this.imageNode))
+ {
+ if (flipH && flipV)
+ {
+ this.imageNode.style.rotation = '180';
+ }
+ else if (flipH)
+ {
+ this.imageNode.style.flip = 'x';
+ }
+ else
+ {
+ this.imageNode.style.flip = 'y';
+ }
+ }
+ else
+ {
+ var filter = (this.imageNode.nodeName == 'DIV') ? 'progid:DXImageTransform.Microsoft.AlphaImageLoader ' +
+ '(src=\'' + this.image + '\', sizingMethod=\'scale\')' : '';
+
+ if (flipH && flipV)
+ {
+ filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
+ }
+ else if (flipH)
+ {
+ filter += 'progid:DXImageTransform.Microsoft.BasicImage(mirror=1)';
+ }
+ else
+ {
+ filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
+ }
+
+ if (this.imageNode.style.filter != filter)
+ {
+ this.imageNode.style.filter = filter;
+ }
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: configureTransparentBackground
+ *
+ * Workaround for security warning in IE if this is used in the overlay pane
+ * of a diagram.
+ */
+mxImageShape.prototype.configureTransparentBackground = function(node)
+{
+ // do nothing
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxImageShape.prototype.redrawSvg = function()
+{
+ this.updateSvgShape(this.innerNode);
+ this.updateSvgShape(this.imageNode);
+
+ if (this.bg != null)
+ {
+ this.updateSvgShape(this.bg);
+
+ if (this.fill != null)
+ {
+ this.bg.setAttribute('fill', this.fill);
+ }
+ else
+ {
+ this.bg.setAttribute('fill', 'none');
+ }
+
+ if (this.stroke != null)
+ {
+ this.bg.setAttribute('stroke', this.stroke);
+ }
+ else
+ {
+ this.bg.setAttribute('stroke', 'none');
+ }
+
+ this.bg.setAttribute('shape-rendering', 'crispEdges');
+ }
+};
+
+/**
+ * Function: configureSvgShape
+ *
+ * Extends method to set opacity on images.
+ */
+mxImageShape.prototype.configureSvgShape = function(node)
+{
+ mxShape.prototype.configureSvgShape.apply(this, arguments);
+
+ if (this.imageNode != null)
+ {
+ if (this.opacity != null)
+ {
+ this.imageNode.setAttribute('opacity', this.opacity / 100);
+ }
+ else
+ {
+ this.imageNode.removeAttribute('opacity');
+ }
+ }
+};
diff --git a/src/js/shape/mxLabel.js b/src/js/shape/mxLabel.js
new file mode 100644
index 0000000..c31f3bf
--- /dev/null
+++ b/src/js/shape/mxLabel.js
@@ -0,0 +1,427 @@
+/**
+ * $Id: mxLabel.js,v 1.40 2012-05-22 16:10:12 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxLabel
+ *
+ * Extends <mxShape> to implement an image shape with a label.
+ * This shape is registered under <mxConstants.SHAPE_LABEL> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxLabel
+ *
+ * Constructs a new label shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxLabel(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxLabel.prototype = new mxShape();
+mxLabel.prototype.constructor = mxLabel;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxLabel.prototype.vmlNodes = mxLabel.prototype.vmlNodes.concat(['label', 'imageNode', 'indicatorImageNode', 'rectNode']);
+
+/**
+ * Variable: imageSize
+ *
+ * Default width and height for the image. Default is
+ * <mxConstants.DEFAULT_IMAGESIZE>.
+ */
+mxLabel.prototype.imageSize = mxConstants.DEFAULT_IMAGESIZE;
+
+/**
+ * Variable: spacing
+ *
+ * Default value for spacing. Default is 2.
+ */
+mxLabel.prototype.spacing = 2;
+
+/**
+ * Variable: indicatorSize
+ *
+ * Default width and height for the indicicator. Default is
+ * 10.
+ */
+mxLabel.prototype.indicatorSize = 10;
+
+/**
+ * Variable: indicatorSpacing
+ *
+ * Default spacing between image and indicator. Default is 2.
+ */
+mxLabel.prototype.indicatorSpacing = 2;
+
+/**
+ * Variable: opaqueVmlImages
+ *
+ * Specifies if all VML images should be rendered without transparency, that
+ * is, if the current opacity should be ignored for images. Default is false.
+ */
+mxLabel.prototype.opaqueVmlImages = false;
+
+/**
+ * Function: init
+ *
+ * Initializes the shape and adds it to the container. This function is
+ * overridden so that the node is already in the DOM when the indicator
+ * is added. This is required to access the ownerSVGelement of the
+ * container in the init function of the indicator.
+ */
+mxLabel.prototype.init = function(container)
+{
+ mxShape.prototype.init.apply(this, arguments);
+
+ // Creates the indicator shape after the node was added to the DOM
+ if (this.indicatorColor != null && this.indicatorShape != null)
+ {
+ this.indicator = new this.indicatorShape();
+ this.indicator.dialect = this.dialect;
+ this.indicator.bounds = this.bounds;
+ this.indicator.fill = this.indicatorColor;
+ this.indicator.stroke = this.indicatorColor;
+ this.indicator.gradient = this.indicatorGradientColor;
+ this.indicator.direction = this.indicatorDirection;
+ this.indicator.init(this.node);
+ this.indicatorShape = null;
+ }
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Reconfigures this shape. This will update the colors of the indicator
+ * and reconfigure it if required.
+ */
+mxLabel.prototype.reconfigure = function()
+{
+ mxShape.prototype.reconfigure.apply(this);
+
+ if (this.indicator != null)
+ {
+ this.indicator.fill = this.indicatorColor;
+ this.indicator.stroke = this.indicatorColor;
+ this.indicator.gradient = this.indicatorGradientColor;
+ this.indicator.direction = this.indicatorDirection;
+ this.indicator.reconfigure();
+ }
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML node to represent this shape.
+ */
+mxLabel.prototype.createHtml = function()
+{
+ var name = 'DIV';
+ var node = document.createElement(name);
+ this.configureHtmlShape(node);
+
+ // Adds a small subshape inside this shape
+ if (this.indicatorImage != null)
+ {
+ this.indicatorImageNode = mxUtils.createImage(this.indicatorImage);
+ this.indicatorImageNode.style.position = 'absolute';
+ node.appendChild(this.indicatorImageNode);
+ }
+
+ // Adds an image node to the div
+ if (this.image != null)
+ {
+ this.imageNode = mxUtils.createImage(this.image);
+ this.stroke = null;
+ this.configureHtmlShape(this.imageNode);
+ mxUtils.setOpacity(this.imageNode, '100');
+ node.appendChild(this.imageNode);
+ }
+
+ return node;
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxLabel.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+
+ // Background
+ var name = (this.isRounded) ? 'v:roundrect' : 'v:rect';
+ this.rectNode = document.createElement(name);
+ this.configureVmlShape(this.rectNode);
+
+ // Disables the shadow and configures the enclosing group
+ this.isShadow = false;
+ this.configureVmlShape(node);
+ node.coordorigin = '0,0';
+ node.appendChild(this.rectNode);
+
+ // Adds a small subshape inside this shape
+ if (this.indicatorImage != null)
+ {
+ this.indicatorImageNode = this.createVmlImage(this.indicatorImage, (this.opaqueVmlImages) ? null : this.opacity);
+ node.appendChild(this.indicatorImageNode);
+ }
+
+ // Adds an image node to the div
+ if (this.image != null)
+ {
+ this.imageNode = this.createVmlImage(this.image, (this.opaqueVmlImages) ? null : this.opacity);
+ node.appendChild(this.imageNode);
+ }
+
+ // Container for the label on top of everything
+ this.label = document.createElement('v:rect');
+ this.label.style.top = '0px'; // relative
+ this.label.style.left = '0px'; // relative
+ this.label.filled = 'false';
+ this.label.stroked = 'false';
+ node.appendChild(this.label);
+
+ return node;
+};
+
+/**
+ * Function: createVmlImage
+ *
+ * Creates an image node for the given image src and opacity to be used in VML.
+ */
+mxLabel.prototype.createVmlImage = function(src, opacity)
+{
+ var result = null;
+
+ // Workaround for data URIs not supported in VML and for added
+ // border around images if opacity is used (not needed in IE9,
+ // but IMG node is probably better and faster anyway).
+ if (src.substring(0, 5) == 'data:' || opacity != null)
+ {
+ result = document.createElement('img');
+ mxUtils.setOpacity(result, opacity);
+ result.setAttribute('border', '0');
+ result.style.position = 'absolute';
+ result.setAttribute('src', src);
+ }
+ else
+ {
+ result = document.createElement('v:image');
+ result.src = src;
+ }
+
+ return result;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node to represent this shape.
+ */
+mxLabel.prototype.createSvg = function()
+{
+ var g = this.createSvgGroup('rect');
+
+ // Adds a small subshape to the svg group
+ if (this.indicatorImage != null)
+ {
+ this.indicatorImageNode = document.createElementNS(mxConstants.NS_SVG, 'image');
+ this.indicatorImageNode.setAttributeNS(mxConstants.NS_XLINK, 'href', this.indicatorImage);
+ g.appendChild(this.indicatorImageNode);
+
+ if (this.opacity != null)
+ {
+ this.indicatorImageNode.setAttribute('opacity', this.opacity / 100);
+ }
+ }
+
+ // Adds an image to the svg group
+ if (this.image != null)
+ {
+ this.imageNode = document.createElementNS(mxConstants.NS_SVG, 'image');
+ this.imageNode.setAttributeNS(mxConstants.NS_XLINK, 'href', this.image);
+
+ if (this.opacity != null)
+ {
+ this.imageNode.setAttribute('opacity', this.opacity / 100);
+ }
+
+ // Disables control-click and alt-click in Firefox
+ this.imageNode.setAttribute('style', 'pointer-events:none');
+ this.configureSvgShape(this.imageNode);
+ g.appendChild(this.imageNode);
+ }
+
+ return g;
+};
+
+/**
+ * Function: redraw
+ *
+ * Overrides redraw to define a unified implementation for redrawing
+ * all supported dialects.
+ */
+mxLabel.prototype.redraw = function()
+{
+ this.updateBoundingBox();
+ var isSvg = (this.dialect == mxConstants.DIALECT_SVG);
+ var isVml = mxUtils.isVml(this.node);
+
+ // Updates the bounds of the outermost shape
+ if (isSvg)
+ {
+ this.updateSvgShape(this.innerNode);
+
+ if (this.shadowNode != null)
+ {
+ this.updateSvgShape(this.shadowNode);
+ }
+
+ this.updateSvgGlassPane();
+ }
+ else if (isVml)
+ {
+ this.updateVmlShape(this.node);
+ this.updateVmlShape(this.rectNode);
+ this.label.style.width = this.node.style.width;
+ this.label.style.height = this.node.style.height;
+
+ this.updateVmlGlassPane();
+ }
+ else
+ {
+ this.updateHtmlShape(this.node);
+ }
+
+ // Updates the imagewidth and imageheight
+ var imageWidth = 0;
+ var imageHeight = 0;
+
+ if (this.imageNode != null)
+ {
+ imageWidth = (this.style[mxConstants.STYLE_IMAGE_WIDTH] ||
+ this.imageSize) * this.scale;
+ imageHeight = (this.style[mxConstants.STYLE_IMAGE_HEIGHT] ||
+ this.imageSize) * this.scale;
+ }
+
+ // Updates the subshape size and location
+ var indicatorSpacing = 0;
+ var indicatorWidth = 0;
+ var indicatorHeight = 0;
+
+ if (this.indicator != null || this.indicatorImageNode != null)
+ {
+ indicatorSpacing = (this.style[mxConstants.STYLE_INDICATOR_SPACING] ||
+ this.indicatorSpacing) * this.scale;
+ indicatorWidth = (this.style[mxConstants.STYLE_INDICATOR_WIDTH] ||
+ this.indicatorSize) * this.scale;
+ indicatorHeight = (this.style[mxConstants.STYLE_INDICATOR_HEIGHT] ||
+ this.indicatorSize) * this.scale;
+ }
+
+ var align = this.style[mxConstants.STYLE_IMAGE_ALIGN];
+ var valign = this.style[mxConstants.STYLE_IMAGE_VERTICAL_ALIGN];
+
+ var inset = this.spacing * this.scale + 5;
+ var width = Math.max(imageWidth, indicatorWidth);
+ var height = imageHeight + indicatorSpacing + indicatorHeight;
+
+ var x = (isSvg) ? this.bounds.x : 0;
+
+ if (align == mxConstants.ALIGN_RIGHT)
+ {
+ x += this.bounds.width - width - inset;
+ }
+ else if (align == mxConstants.ALIGN_CENTER)
+ {
+ x += (this.bounds.width - width) / 2;
+ }
+ else // default is left
+ {
+ x += inset;
+ }
+
+ var y = (isSvg) ? this.bounds.y : 0;
+
+ if (valign == mxConstants.ALIGN_BOTTOM)
+ {
+ y += this.bounds.height - height - inset;
+ }
+ else if (valign == mxConstants.ALIGN_TOP)
+ {
+ y += inset;
+ }
+ else // default is middle
+ {
+ y += (this.bounds.height - height) / 2;
+ }
+
+ // Updates the imagenode
+ if (this.imageNode != null)
+ {
+ if (isSvg)
+ {
+ this.imageNode.setAttribute('x', (x + (width - imageWidth) / 2) + 'px');
+ this.imageNode.setAttribute('y', y + 'px');
+ this.imageNode.setAttribute('width', imageWidth + 'px');
+ this.imageNode.setAttribute('height', imageHeight + 'px');
+ }
+ else
+ {
+ this.imageNode.style.left = (x + width - imageWidth) + 'px';
+ this.imageNode.style.top = y + 'px';
+ this.imageNode.style.width = imageWidth + 'px';
+ this.imageNode.style.height = imageHeight + 'px';
+ this.imageNode.stroked = 'false';
+ }
+ }
+
+ // Updates the subshapenode (aka. indicator)
+ if (this.indicator != null)
+ {
+ this.indicator.bounds = new mxRectangle(
+ x + (width - indicatorWidth) / 2,
+ y + imageHeight + indicatorSpacing,
+ indicatorWidth, indicatorHeight);
+ this.indicator.redraw();
+ }
+ else if (this.indicatorImageNode != null)
+ {
+ if (isSvg)
+ {
+ this.indicatorImageNode.setAttribute('x', (x + (width - indicatorWidth) / 2) + 'px');
+ this.indicatorImageNode.setAttribute('y', (y + imageHeight + indicatorSpacing) + 'px');
+ this.indicatorImageNode.setAttribute('width', indicatorWidth + 'px');
+ this.indicatorImageNode.setAttribute('height', indicatorHeight + 'px');
+ }
+ else
+ {
+ this.indicatorImageNode.style.left = (x + (width - indicatorWidth) / 2) + 'px';
+ this.indicatorImageNode.style.top = (y + imageHeight + indicatorSpacing) + 'px';
+ this.indicatorImageNode.style.width = indicatorWidth + 'px';
+ this.indicatorImageNode.style.height = indicatorHeight + 'px';
+ }
+ }
+};
diff --git a/src/js/shape/mxLine.js b/src/js/shape/mxLine.js
new file mode 100644
index 0000000..5ef3eb0
--- /dev/null
+++ b/src/js/shape/mxLine.js
@@ -0,0 +1,217 @@
+/**
+ * $Id: mxLine.js,v 1.36 2012-03-30 04:44:59 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxLine
+ *
+ * Extends <mxShape> to implement a horizontal line shape.
+ * This shape is registered under <mxConstants.SHAPE_LINE> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxLine
+ *
+ * Constructs a new line shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * stroke - String that defines the stroke color. Default is 'black'. This is
+ * stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxLine(bounds, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxLine.prototype = new mxShape();
+mxLine.prototype.constructor = mxLine;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxLine.prototype.vmlNodes = mxLine.prototype.vmlNodes.concat(['label', 'innerNode']);
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxLine.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxLine.prototype.preferModeHtml = false;
+
+/**
+ * Function: clone
+ *
+ * Overrides the clone method to add special fields.
+ */
+mxLine.prototype.clone = function()
+{
+ var clone = new mxLine(this.bounds,
+ this.stroke, this.strokewidth);
+ clone.isDashed = this.isDashed;
+
+ return clone;
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxLine.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+ node.style.position = 'absolute';
+
+ // Represents the text label container
+ this.label = document.createElement('v:rect');
+ this.label.style.position = 'absolute';
+ this.label.stroked = 'false';
+ this.label.filled = 'false';
+ node.appendChild(this.label);
+
+ // Represents the straight line shape
+ this.innerNode = document.createElement('v:shape');
+ this.configureVmlShape(this.innerNode);
+ node.appendChild(this.innerNode);
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Redraws this VML shape by invoking <updateVmlShape> on this.node.
+ */
+mxLine.prototype.reconfigure = function()
+{
+ if (mxUtils.isVml(this.node))
+ {
+ this.configureVmlShape(this.innerNode);
+ }
+ else
+ {
+ mxShape.prototype.reconfigure.apply(this, arguments);
+ }
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxLine.prototype.redrawVml = function()
+{
+ this.updateVmlShape(this.node);
+ this.updateVmlShape(this.label);
+
+ this.innerNode.coordsize = this.node.coordsize;
+ this.innerNode.strokeweight = (this.strokewidth * this.scale) + 'px';
+ this.innerNode.style.width = this.node.style.width;
+ this.innerNode.style.height = this.node.style.height;
+
+ var w = this.bounds.width;
+ var h =this.bounds.height;
+
+ if (this.direction == mxConstants.DIRECTION_NORTH ||
+ this.direction == mxConstants.DIRECTION_SOUTH)
+ {
+ this.innerNode.path = 'm ' + Math.round(w / 2) + ' 0' +
+ ' l ' + Math.round(w / 2) + ' ' + Math.round(h) + ' e';
+ }
+ else
+ {
+ this.innerNode.path = 'm 0 ' + Math.round(h / 2) +
+ ' l ' + Math.round(w) + ' ' + Math.round(h / 2) + ' e';
+ }
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxLine.prototype.createSvg = function()
+{
+ var g = this.createSvgGroup('path');
+
+ // Creates an invisible shape around the path for easier
+ // selection with the mouse. Note: Firefox does not ignore
+ // the value of the stroke attribute for pointer-events: stroke.
+ // It does, however, ignore the visibility attribute.
+ this.pipe = this.createSvgPipe();
+ g.appendChild(this.pipe);
+
+ return g;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxLine.prototype.redrawSvg = function()
+{
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ this.innerNode.setAttribute('stroke-width', strokeWidth);
+
+ if (this.bounds != null)
+ {
+ var x = this.bounds.x;
+ var y = this.bounds.y;
+ var w = this.bounds.width;
+ var h = this.bounds.height;
+
+ var d = null;
+
+ if (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH)
+ {
+ d = 'M ' + Math.round(x + w / 2) + ' ' + Math.round(y) + ' L ' + Math.round(x + w / 2) + ' ' + Math.round(y + h);
+ }
+ else
+ {
+ d = 'M ' + Math.round(x) + ' ' + Math.round(y + h / 2) + ' L ' + Math.round(x + w) + ' ' + Math.round(y + h / 2);
+ }
+
+ this.innerNode.setAttribute('d', d);
+ this.pipe.setAttribute('d', d);
+ this.pipe.setAttribute('stroke-width', this.strokewidth + mxShape.prototype.SVG_STROKE_TOLERANCE);
+
+ this.updateSvgTransform(this.innerNode, false);
+ this.updateSvgTransform(this.pipe, false);
+
+ if (this.crisp)
+ {
+ this.innerNode.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.innerNode.removeAttribute('shape-rendering');
+ }
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ this.innerNode.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+ }
+};
diff --git a/src/js/shape/mxMarker.js b/src/js/shape/mxMarker.js
new file mode 100644
index 0000000..cfd6f66
--- /dev/null
+++ b/src/js/shape/mxMarker.js
@@ -0,0 +1,267 @@
+/**
+ * $Id: mxMarker.js,v 1.19 2012-03-30 12:51:58 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxMarker =
+{
+ /**
+ * Class: mxMarker
+ *
+ * A static class that implements all markers for VML and SVG using a
+ * registry. NOTE: The signatures in this class will change.
+ *
+ * Variable: markers
+ *
+ * Maps from markers names to functions to paint the markers.
+ */
+ markers: [],
+
+ /**
+ * Function: paintMarker
+ *
+ * Paints the given marker.
+ */
+ paintMarker: function(node, type, p0, pe, color, strokewidth, size, scale, x0, y0, source, style)
+ {
+ var marker = mxMarker.markers[type];
+ var result = null;
+
+ if (marker != null)
+ {
+ var isVml = mxUtils.isVml(node);
+
+ // Computes the norm and the inverse norm
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+
+ if (isNaN(dx) || isNaN(dy))
+ {
+ return;
+ }
+
+ var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+ var nx = dx * scale / dist;
+ var ny = dy * scale / dist;
+
+ pe = pe.clone();
+
+ if (isVml)
+ {
+ pe.x -= x0;
+ pe.y -= y0;
+ }
+
+ // Handles start-/endFill style
+ var filled = true;
+ var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL;
+
+ if (style[key] == 0)
+ {
+ filled = false;
+ }
+
+ if (isVml)
+ {
+ // Opacity is updated in reconfigure, use nf in path for no fill
+ node.strokecolor = color;
+
+ if (filled)
+ {
+ node.fillcolor = color;
+ }
+ else
+ {
+ node.filled = 'false';
+ }
+ }
+ else
+ {
+ node.setAttribute('stroke', color);
+
+ var op = (style.opacity != null) ? style.opacity / 100 : 1;
+ node.setAttribute('stroke-opacity', op);
+
+ if (filled)
+ {
+ node.setAttribute('fill', color);
+ node.setAttribute('fill-opacity', op);
+ }
+ else
+ {
+ node.setAttribute('fill', 'none');
+ }
+ }
+
+ result = marker.call(this, node, type, pe, nx, ny, strokewidth, size, scale, isVml);
+ }
+
+ return result;
+ }
+
+};
+
+(function()
+{
+ /**
+ * Drawing of the classic and block arrows.
+ */
+ var tmp = function(node, type, pe, nx, ny, strokewidth, size, scale, isVml)
+ {
+ // The angle of the forward facing arrow sides against the x axis is
+ // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+ // only half the strokewidth is processed ).
+ var endOffsetX = nx * strokewidth * 1.118;
+ var endOffsetY = ny * strokewidth * 1.118;
+ pe.x -= endOffsetX;
+ pe.y -= endOffsetY;
+
+ nx = nx * (size + strokewidth);
+ ny = ny * (size + strokewidth);
+
+ if (isVml)
+ {
+ node.path = 'm' + Math.round(pe.x) + ',' + Math.round(pe.y) +
+ ' l' + Math.round(pe.x - nx - ny / 2) + ' ' + Math.round(pe.y - ny + nx / 2) +
+ ((type != mxConstants.ARROW_CLASSIC) ? '' :
+ ' ' + Math.round(pe.x - nx * 3 / 4) + ' ' + Math.round(pe.y - ny * 3 / 4)) +
+ ' ' + Math.round(pe.x + ny / 2 - nx) + ' ' + Math.round(pe.y - ny - nx / 2) +
+ ' x e';
+ node.setAttribute('strokeweight', (strokewidth * scale) + 'px');
+ }
+ else
+ {
+ node.setAttribute('d', 'M ' + pe.x + ' ' + pe.y +
+ ' L ' + (pe.x - nx - ny / 2) + ' ' + (pe.y - ny + nx / 2) +
+ ((type != mxConstants.ARROW_CLASSIC) ? '' :
+ ' L ' + (pe.x - nx * 3 / 4) + ' ' + (pe.y - ny * 3 / 4)) +
+ ' L ' + (pe.x + ny / 2 - nx) + ' ' + (pe.y - ny - nx / 2) +
+ ' z');
+ node.setAttribute('stroke-width', strokewidth * scale);
+ }
+
+ var f = (type != mxConstants.ARROW_CLASSIC) ? 1 : 3 / 4;
+ return new mxPoint(-nx * f - endOffsetX, -ny * f - endOffsetY);
+ };
+
+ mxMarker.markers[mxConstants.ARROW_CLASSIC] = tmp;
+ mxMarker.markers[mxConstants.ARROW_BLOCK] = tmp;
+}());
+
+mxMarker.markers[mxConstants.ARROW_OPEN] = function(node, type, pe, nx, ny, strokewidth, size, scale, isVml)
+{
+ // The angle of the forward facing arrow sides against the x axis is
+ // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+ // only half the strokewidth is processed ).
+ var endOffsetX = nx * strokewidth * 1.118;
+ var endOffsetY = ny * strokewidth * 1.118;
+ pe.x -= endOffsetX;
+ pe.y -= endOffsetY;
+
+ nx = nx * (size + strokewidth);
+ ny = ny * (size + strokewidth);
+
+ if (isVml)
+ {
+ node.path = 'm' + Math.round(pe.x - nx - ny / 2) + ' ' + Math.round(pe.y - ny + nx / 2) +
+ ' l' + Math.round(pe.x) + ' ' + Math.round(pe.y) +
+ ' ' + Math.round(pe.x + ny / 2 - nx) + ' ' + Math.round(pe.y - ny - nx / 2) +
+ ' e nf';
+ node.setAttribute('strokeweight', (strokewidth * scale) + 'px');
+ }
+ else
+ {
+ node.setAttribute('d', 'M ' + (pe.x - nx - ny / 2) + ' ' + (pe.y - ny + nx / 2) +
+ ' L ' + (pe.x) + ' ' + (pe.y) +
+ ' L ' + (pe.x + ny / 2 - nx) + ' ' + (pe.y - ny - nx / 2));
+ node.setAttribute('stroke-width', strokewidth * scale);
+ node.setAttribute('fill', 'none');
+ }
+
+ return new mxPoint(-endOffsetX * 2, -endOffsetY * 2);
+};
+
+mxMarker.markers[mxConstants.ARROW_OVAL] = function(node, type, pe, nx, ny, strokewidth, size, scale, isVml)
+{
+ nx *= size;
+ ny *= size;
+
+ nx *= 0.5 + strokewidth / 2;
+ ny *= 0.5 + strokewidth / 2;
+
+ var absSize = size * scale;
+ var radius = absSize / 2;
+
+ if (isVml)
+ {
+ node.path = 'm' + Math.round(pe.x + radius) + ' ' + Math.round(pe.y) +
+ ' at ' + Math.round(pe.x - radius) + ' ' + Math.round(pe.y - radius) +
+ ' ' + Math.round(pe.x + radius) + ' ' + Math.round(pe.y + radius) +
+ ' ' + Math.round(pe.x + radius) + ' ' + Math.round(pe.y) +
+ ' ' + Math.round(pe.x + radius) + ' ' + Math.round(pe.y) +
+ ' x e';
+
+ node.setAttribute('strokeweight', (strokewidth * scale) + 'px');
+ }
+ else
+ {
+ node.setAttribute('d', 'M ' + (pe.x - radius) + ' ' + (pe.y) +
+ ' a ' + (radius) + ' ' + (radius) +
+ ' 0 1,1 ' + (absSize) + ' 0' +
+ ' a ' + (radius) + ' ' + (radius) +
+ ' 0 1,1 ' + (-absSize) + ' 0 z');
+ node.setAttribute('stroke-width', strokewidth * scale);
+ }
+
+ return new mxPoint(-nx / (2 + strokewidth), -ny / (2 + strokewidth));
+};
+
+(function()
+ {
+ /**
+ * Drawing of the diamond and thin diamond markers
+ */
+ var tmp_diamond = function(node, type, pe, nx, ny, strokewidth, size, scale, isVml)
+ {
+ // The angle of the forward facing arrow sides against the x axis is
+ // 45 degrees, 1/sin(45) = 1.4142 / 2 = 0.7071 ( / 2 allows for
+ // only half the strokewidth is processed ). Or 0.9862 for thin diamond.
+ // Note these values and the tk variable below are dependent, update
+ // both together (saves trig hard coding it).
+ var swFactor = (type == mxConstants.ARROW_DIAMOND) ? 0.7071 : 0.9862;
+ var endOffsetX = nx * strokewidth * swFactor;
+ var endOffsetY = ny * strokewidth * swFactor;
+
+ nx = nx * (size + strokewidth);
+ ny = ny * (size + strokewidth);
+
+ pe.x -= endOffsetX + nx / 2;
+ pe.y -= endOffsetY + ny / 2;
+
+ // thickness factor for diamond
+ var tk = ((type == mxConstants.ARROW_DIAMOND) ? 2 : 3.4);
+
+ if (isVml)
+ {
+ node.path = 'm' + Math.round(pe.x + nx / 2) + ' ' + Math.round(pe.y + ny / 2) +
+ ' l' + Math.round(pe.x - ny / tk) + ' ' + Math.round(pe.y + nx / tk) +
+ ' ' + Math.round(pe.x - nx / 2) + ' ' + Math.round(pe.y - ny / 2) +
+ ' ' + Math.round(pe.x + ny / tk) + ' ' + Math.round(pe.y - nx / tk) +
+ ' x e';
+ node.setAttribute('strokeweight', (strokewidth * scale) + 'px');
+ }
+ else
+ {
+ node.setAttribute('d', 'M ' + (pe.x + nx / 2) + ' ' + (pe.y + ny / 2) +
+ ' L ' + (pe.x - ny / tk) + ' ' + (pe.y + nx / tk) +
+ ' L ' + (pe.x - nx / 2) + ' ' + (pe.y - ny / 2) +
+ ' L ' + (pe.x + ny / tk) + ' ' + (pe.y - nx / tk) +
+ ' z');
+ node.setAttribute('stroke-width', strokewidth * scale);
+ }
+
+ return new mxPoint(-endOffsetX - nx, -endOffsetY - ny);
+ };
+
+ mxMarker.markers[mxConstants.ARROW_DIAMOND] = tmp_diamond;
+ mxMarker.markers[mxConstants.ARROW_DIAMOND_THIN] = tmp_diamond;
+ }());
diff --git a/src/js/shape/mxPolyline.js b/src/js/shape/mxPolyline.js
new file mode 100644
index 0000000..2d64323
--- /dev/null
+++ b/src/js/shape/mxPolyline.js
@@ -0,0 +1,146 @@
+/**
+ * $Id: mxPolyline.js,v 1.31 2012-05-24 12:00:45 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPolyline
+ *
+ * Extends <mxShape> to implement a polyline (a line with multiple points).
+ * This shape is registered under <mxConstants.SHAPE_POLYLINE> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxPolyline
+ *
+ * Constructs a new polyline shape.
+ *
+ * Parameters:
+ *
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * stroke - String that defines the stroke color. Default is 'black'. This is
+ * stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxPolyline(points, stroke, strokewidth)
+{
+ this.points = points;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxPolyline.prototype = new mxShape();
+mxPolyline.prototype.constructor = mxPolyline;
+
+/**
+ * Variable: addPipe
+ *
+ * Specifies if a SVG path should be created around any path to increase the
+ * tolerance for mouse events. Default is false since this shape is filled.
+ */
+mxPolyline.prototype.addPipe = true;
+
+/**
+ * Function: create
+ *
+ * Override to create HTML regardless of gradient and
+ * rounded property.
+ */
+mxPolyline.prototype.create = function()
+{
+ var node = null;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ node = this.createSvg();
+ }
+ else if (this.dialect == mxConstants.DIALECT_STRICTHTML ||
+ (this.dialect == mxConstants.DIALECT_PREFERHTML &&
+ this.points != null && this.points.length > 0))
+ {
+ node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+ node.style.borderStyle = '';
+ node.style.background = '';
+ }
+ else
+ {
+ node = document.createElement('v:shape');
+ this.configureVmlShape(node);
+ var strokeNode = document.createElement('v:stroke');
+
+ if (this.opacity != null)
+ {
+ strokeNode.opacity = this.opacity + '%';
+ }
+
+ node.appendChild(strokeNode);
+ }
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Overrides the method to update the bounds if they have not been
+ * assigned.
+ */
+mxPolyline.prototype.redrawVml = function()
+{
+ // Updates the bounds based on the points
+ if (this.points != null && this.points.length > 0 && this.points[0] != null)
+ {
+ this.bounds = new mxRectangle(this.points[0].x,this.points[0].y, 0, 0);
+
+ for (var i = 1; i < this.points.length; i++)
+ {
+ this.bounds.add(new mxRectangle(this.points[i].x,this.points[i].y, 0, 0));
+ }
+ }
+
+ mxShape.prototype.redrawVml.apply(this, arguments);
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxPolyline.prototype.createSvg = function()
+{
+ var g = this.createSvgGroup('path');
+
+ // Creates an invisible shape around the path for easier
+ // selection with the mouse. Note: Firefox does not ignore
+ // the value of the stroke attribute for pointer-events: stroke,
+ // it does, however, ignore the visibility attribute.
+ if (this.addPipe)
+ {
+ this.pipe = this.createSvgPipe();
+ g.appendChild(this.pipe);
+ }
+
+ return g;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxPolyline.prototype.redrawSvg = function()
+{
+ this.updateSvgShape(this.innerNode);
+ var d = this.innerNode.getAttribute('d');
+
+ if (d != null && this.pipe != null)
+ {
+ this.pipe.setAttribute('d', d);
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ this.pipe.setAttribute('stroke-width', strokeWidth + mxShape.prototype.SVG_STROKE_TOLERANCE);
+ }
+};
diff --git a/src/js/shape/mxRectangleShape.js b/src/js/shape/mxRectangleShape.js
new file mode 100644
index 0000000..26688c3
--- /dev/null
+++ b/src/js/shape/mxRectangleShape.js
@@ -0,0 +1,61 @@
+/**
+ * $Id: mxRectangleShape.js,v 1.17 2012-09-26 07:51:29 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxRectangleShape
+ *
+ * Extends <mxShape> to implement a rectangle shape.
+ * This shape is registered under <mxConstants.SHAPE_RECTANGLE>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxRectangleShape
+ *
+ * Constructs a new rectangle shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxRectangleShape(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxRectangleShape.prototype = new mxShape();
+mxRectangleShape.prototype.constructor = mxRectangleShape;
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxRectangleShape.prototype.createVml = function()
+{
+ var name = (this.isRounded) ? 'v:roundrect' : 'v:rect';
+ var node = document.createElement(name);
+ this.configureVmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node to represent this shape.
+ */
+mxRectangleShape.prototype.createSvg = function()
+{
+ return this.createSvgGroup('rect');
+};
diff --git a/src/js/shape/mxRhombus.js b/src/js/shape/mxRhombus.js
new file mode 100644
index 0000000..37e35ec
--- /dev/null
+++ b/src/js/shape/mxRhombus.js
@@ -0,0 +1,172 @@
+/**
+ * $Id: mxRhombus.js,v 1.25 2012-04-04 07:34:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxRhombus
+ *
+ * Extends <mxShape> to implement a rhombus (aka diamond) shape.
+ * This shape is registered under <mxConstants.SHAPE_RHOMBUS>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxRhombus
+ *
+ * Constructs a new rhombus shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxRhombus(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxRhombus.prototype = new mxShape();
+mxRhombus.prototype.constructor = mxRhombus;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxRhombus.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxRhombus.prototype.preferModeHtml = false;
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML node to represent this shape.
+ */
+mxRhombus.prototype.createHtml = function()
+{
+ var node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node(s) to represent this shape.
+ */
+mxRhombus.prototype.createVml = function()
+{
+ var node = document.createElement('v:shape');
+ this.configureVmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxRhombus.prototype.createSvg = function()
+{
+ return this.createSvgGroup('path');
+};
+
+// TODO: When used as an indicator, this.node.points is null
+// so we use a path object for building general diamonds.
+//mxRhombus.prototype.redraw = function() {
+// this.node.setAttribute('strokeweight', (this.strokewidth * this.scale) + 'px');
+// var x = this.bounds.x;
+// var y = this.bounds.y;
+// var w = this.bounds.width;
+// var h = this.bounds.height;
+// this.node.points.value = (x+w/2)+','+y+' '+(x+w)+','+(y+h/2)+
+// ' '+(x+w/2)+','+(y+h)+' '+x+','+(y+h/2)+' '+
+// (x+w/2)+','+y;
+//}
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxRhombus.prototype.redrawVml = function()
+{
+ this.updateVmlShape(this.node);
+ var x = 0;
+ var y = 0;
+ var w = Math.round(this.bounds.width);
+ var h = Math.round(this.bounds.height);
+
+ this.node.path = 'm ' + Math.round(x + w / 2) + ' ' + y +
+ ' l ' + (x + w) + ' ' + Math.round(y + h / 2) +
+ ' l ' + Math.round(x + w / 2) + ' ' + (y + h) +
+ ' l ' + x + ' ' + Math.round(y + h / 2) + ' x e';
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxRhombus.prototype.redrawHtml = function()
+{
+ this.updateHtmlShape(this.node);
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxRhombus.prototype.redrawSvg = function()
+{
+ this.updateSvgNode(this.innerNode);
+
+ if (this.shadowNode != null)
+ {
+ this.updateSvgNode(this.shadowNode);
+ }
+};
+
+/**
+ * Function: createSvgSpan
+ *
+ * Updates the path for the given SVG node.
+ */
+mxRhombus.prototype.updateSvgNode = function(node)
+{
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ node.setAttribute('stroke-width', strokeWidth);
+ var x = this.bounds.x;
+ var y = this.bounds.y;
+ var w = this.bounds.width;
+ var h = this.bounds.height;
+ var d = 'M ' + Math.round(x + w / 2) + ' ' + Math.round(y) + ' L ' + Math.round(x + w) + ' ' + Math.round(y + h / 2) +
+ ' L ' + Math.round(x + w / 2) + ' ' + Math.round(y + h) + ' L ' + Math.round(x) + ' ' + Math.round(y + h / 2) +
+ ' Z ';
+ node.setAttribute('d', d);
+ this.updateSvgTransform(node, node == this.shadowNode);
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ node.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+};
diff --git a/src/js/shape/mxShape.js b/src/js/shape/mxShape.js
new file mode 100644
index 0000000..44ba3e7
--- /dev/null
+++ b/src/js/shape/mxShape.js
@@ -0,0 +1,2045 @@
+/**
+ * $Id: mxShape.js,v 1.173 2012-07-31 11:46:53 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxShape
+ *
+ * Base class for all shapes. A shape in mxGraph is a
+ * separate implementation for SVG, VML and HTML. Which
+ * implementation to use is controlled by the <dialect>
+ * property which is assigned from within the <mxCellRenderer>
+ * when the shape is created. The dialect must be assigned
+ * for a shape, and it does normally depend on the browser and
+ * the confiuration of the graph (see <mxGraph> rendering hint).
+ *
+ * For each supported shape in SVG and VML, a corresponding
+ * shape exists in mxGraph, namely for text, image, rectangle,
+ * rhombus, ellipse and polyline. The other shapes are a
+ * combination of these shapes (eg. label and swimlane)
+ * or they consist of one or more (filled) path objects
+ * (eg. actor and cylinder). The HTML implementation is
+ * optional but may be required for a HTML-only view of
+ * the graph.
+ *
+ * Custom Shapes:
+ *
+ * To extend from this class, the basic code looks as follows.
+ * In the special case where the custom shape consists only of
+ * one filled region or one filled region and an additional stroke
+ * the <mxActor> and <mxCylinder> should be subclassed,
+ * respectively. These implement <redrawPath> in order to create
+ * the path expression for VML and SVG via a unified API (see
+ * <mxPath>). <mxCylinder.redrawPath> has an additional boolean
+ * argument to draw the foreground and background separately.
+ *
+ * (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)
+ * graph.cellRenderer.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() { };
+
+/**
+ * Variable: SVG_STROKE_TOLERANCE
+ *
+ * Event-tolerance for SVG strokes (in px). Default is 8.
+ */
+mxShape.prototype.SVG_STROKE_TOLERANCE = 8;
+
+/**
+ * Variable: scale
+ *
+ * Holds the scale in which the shape is being painted.
+ */
+mxShape.prototype.scale = 1;
+
+/**
+ * Variable: dialect
+ *
+ * Holds the dialect in which the shape is to be painted.
+ * This can be one of the DIALECT constants in <mxConstants>.
+ */
+mxShape.prototype.dialect = null;
+
+/**
+ * Variable: crisp
+ *
+ * Special attribute for SVG rendering to set the shape-rendering attribute to
+ * crispEdges in the output. This is ignored in IE. Default is false. To
+ * disable antialias in IE, the explorer.css file can be changed as follows:
+ *
+ * [code]
+ * v\:* {
+ * behavior: url(#default#VML);
+ * antialias: false;
+ * }
+ * [/code]
+ */
+mxShape.prototype.crisp = false;
+
+/**
+ * Variable: roundedCrispSvg
+ *
+ * Specifies if crisp rendering should be enabled for rounded shapes.
+ * Default is true.
+ */
+mxShape.prototype.roundedCrispSvg = true;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Specifies if <createHtml> should be used in mixed Html mode.
+ * Default is true.
+ */
+mxShape.prototype.mixedModeHtml = true;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Specifies if <createHtml> should be used in prefer Html mode.
+ * Default is true.
+ */
+mxShape.prototype.preferModeHtml = true;
+
+/**
+ * Variable: bounds
+ *
+ * Holds the <mxRectangle> that specifies the bounds of this shape.
+ */
+mxShape.prototype.bounds = null;
+
+/**
+ * Variable: points
+ *
+ * Holds the array of <mxPoints> that specify the points of this shape.
+ */
+mxShape.prototype.points = null;
+
+/**
+ * Variable: node
+ *
+ * Holds the outermost DOM node that represents this shape.
+ */
+mxShape.prototype.node = null;
+
+/**
+ * Variable: label
+ *
+ * Reference to the DOM node that should contain the label. This is null
+ * if the label should be placed inside <node> or <innerNode>.
+ */
+mxShape.prototype.label = null;
+
+/**
+ * Variable: innerNode
+ *
+ * Holds the DOM node that graphically represents this shape. This may be
+ * null if the outermost DOM <node> represents this shape.
+ */
+mxShape.prototype.innerNode = null;
+
+/**
+ * Variable: style
+ *
+ * Holds the style of the cell state that corresponds to this shape. This may
+ * be null if the shape is used directly, without a cell state.
+ */
+mxShape.prototype.style = null;
+
+/**
+ * Variable: startOffset
+ *
+ * Specifies the offset in pixels from the first point in <points> and
+ * the actual start of the shape.
+ */
+mxShape.prototype.startOffset = null;
+
+/**
+ * Variable: endOffset
+ *
+ * Specifies the offset in pixels from the last point in <points> and
+ * the actual start of the shape.
+ */
+mxShape.prototype.endOffset = 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: vmlNodes
+ *
+ * Array if VML node names to fix in IE8 standards mode.
+ */
+mxShape.prototype.vmlNodes = ['node', 'strokeNode', 'fillNode', 'shadowNode'];
+
+/**
+ * Variable: vmlScale
+ *
+ * Internal scaling for VML using coordsize for better precision.
+ */
+mxShape.prototype.vmlScale = 1;
+
+/**
+ * Variable: strokewidth
+ *
+ * Holds the current strokewidth. Default is 1.
+ */
+mxShape.prototype.strokewidth = 1;
+
+/**
+ * Function: setCursor
+ *
+ * Sets the cursor on the given shape.
+ *
+ * Parameters:
+ *
+ * cursor - The cursor to be used.
+ */
+mxShape.prototype.setCursor = function(cursor)
+{
+ if (cursor == null)
+ {
+ cursor = '';
+ }
+
+ this.cursor = cursor;
+
+ if (this.innerNode != null)
+ {
+ this.innerNode.style.cursor = cursor;
+ }
+
+ if (this.node != null)
+ {
+ this.node.style.cursor = cursor;
+ }
+
+ if (this.pipe != null)
+ {
+ this.pipe.style.cursor = cursor;
+ }
+};
+
+/**
+ * Function: getCursor
+ *
+ * Returns the current cursor.
+ */
+mxShape.prototype.getCursor = function()
+{
+ return this.cursor;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the shape by creaing the DOM node using <create>
+ * and adding it into the given container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the shape.
+ */
+mxShape.prototype.init = function(container)
+{
+ if (this.node == null)
+ {
+ this.node = this.create(container);
+
+ if (container != null)
+ {
+ container.appendChild(this.node);
+
+ // Workaround for broken VML in IE8 standards mode. This gives an ID to
+ // each element that is referenced from this instance. After adding the
+ // DOM to the document, the outerHTML is overwritten to fix the VML
+ // rendering and the references are restored.
+ if (document.documentMode == 8 && mxUtils.isVml(this.node))
+ {
+ this.reparseVml();
+ }
+ }
+ }
+
+ // Gradients are inserted late when the owner SVG element is known
+ if (this.insertGradientNode != null)
+ {
+ this.insertGradient(this.insertGradientNode);
+ this.insertGradientNode = null;
+ }
+};
+
+/**
+ * Function: reparseVml
+ *
+ * Forces a parsing of the outerHTML of this node and restores all references specified in <vmlNodes>.
+ * This is a workaround for the VML rendering bug in IE8 standards mode.
+ */
+mxShape.prototype.reparseVml = function()
+{
+ // Assigns temporary IDs to VML nodes so that references can be restored when
+ // inserted into the DOM as a string
+ for (var i = 0; i < this.vmlNodes.length; i++)
+ {
+ if (this[this.vmlNodes[i]] != null)
+ {
+ this[this.vmlNodes[i]].setAttribute('id', 'mxTemporaryReference-' + this.vmlNodes[i]);
+ }
+ }
+
+ this.node.outerHTML = this.node.outerHTML;
+
+ // Restores references to the actual DOM nodes
+ for (var i = 0; i < this.vmlNodes.length; i++)
+ {
+ if (this[this.vmlNodes[i]] != null)
+ {
+ this[this.vmlNodes[i]] = this.node.ownerDocument.getElementById('mxTemporaryReference-' + this.vmlNodes[i]);
+ this[this.vmlNodes[i]].removeAttribute('id');
+ }
+ }
+};
+
+/**
+ * Function: insertGradient
+ *
+ * Inserts the given gradient node.
+ */
+mxShape.prototype.insertGradient = function(node)
+{
+ // Gradients are inserted late when the owner SVG element is known
+ if (node != null)
+ {
+ // Checks if the given gradient already exists inside the SVG element
+ // that also contains the node that represents this shape. If the gradient
+ // with the same ID exists in another SVG element, then this will add
+ // a copy of the gradient with a different ID to the SVG element and update
+ // the reference accordingly. This is required in Firefox because if the
+ // referenced fill element is removed from the DOM the shape appears black.
+ var count = 0;
+ var id = node.getAttribute('id');
+ var gradient = document.getElementById(id);
+
+ while (gradient != null && gradient.ownerSVGElement != this.node.ownerSVGElement)
+ {
+ count++;
+ id = node.getAttribute('id') + '-' + count;
+ gradient = document.getElementById(id);
+ }
+
+ // According to specification, gradients should be put in a defs
+ // section in the first child of the owner SVG element. However,
+ // it turns out that gradients only work when added as follows.
+ if (gradient == null)
+ {
+ node.setAttribute('id', id);
+ this.node.ownerSVGElement.appendChild(node);
+ gradient = node;
+ }
+
+ if (gradient != null)
+ {
+ var ref = 'url(#' + id + ')';
+ var tmp = (this.innerNode != null) ? this.innerNode : this.node;
+
+ if (tmp != null && tmp.getAttribute('fill') != ref)
+ {
+ tmp.setAttribute('fill', ref);
+ }
+ }
+ }
+};
+
+/**
+ * Function: isMixedModeHtml
+ *
+ * Used to determine if a shape can be rendered using <createHtml> in mixed
+ * mode Html without compromising the display accuracy. The default
+ * implementation will check if the shape is not rounded or rotated and has
+ * no gradient, and will use a DIV if that is the case. It will also check
+ * if <mxShape.mixedModeHtml> is true, which is the default settings.
+ * Subclassers can either override <mixedModeHtml> or this function if the
+ * result depends on dynamic values. The graph's dialect is available via
+ * <dialect>.
+ */
+mxShape.prototype.isMixedModeHtml = function()
+{
+ return this.mixedModeHtml && !this.isRounded && !this.isShadow && this.gradient == null &&
+ mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, 0) == 0 &&
+ mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, 0) == 0;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM node(s) for the shape in
+ * the given container. This implementation invokes
+ * <createSvg>, <createHtml> or <createVml> depending
+ * on the <dialect> and style settings.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the shape.
+ */
+mxShape.prototype.create = function(container)
+{
+ var node = null;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ node = this.createSvg();
+ }
+ else if (this.dialect == mxConstants.DIALECT_STRICTHTML ||
+ (this.preferModeHtml && this.dialect == mxConstants.DIALECT_PREFERHTML) ||
+ (this.isMixedModeHtml() && this.dialect == mxConstants.DIALECT_MIXEDHTML))
+ {
+ node = this.createHtml();
+ }
+ else
+ {
+ node = this.createVml();
+ }
+
+ return node;
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML DOM node(s) to represent
+ * this shape. This implementation falls back to <createVml>
+ * so that the HTML creation is optional.
+ */
+mxShape.prototype.createHtml = function()
+{
+ var node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the shape by removing it from the DOM and releasing the DOM
+ * node associated with the shape using <mxEvent.release>.
+ */
+mxShape.prototype.destroy = function()
+{
+ if (this.node != null)
+ {
+ mxEvent.release(this.node);
+
+ if (this.node.parentNode != null)
+ {
+ this.node.parentNode.removeChild(this.node);
+ }
+
+ if (this.node.glassOverlay)
+ {
+ this.node.glassOverlay.parentNode.removeChild(this.node.glassOverlay);
+ this.node.glassOverlay = null;
+ }
+
+ this.node = null;
+ }
+};
+
+/**
+ * Function: apply
+ *
+ * Applies the style of the given <mxCellState> to the shape. This
+ * implementation assigns the following styles to local fields:
+ *
+ * - <mxConstants.STYLE_FILLCOLOR> => fill
+ * - <mxConstants.STYLE_GRADIENTCOLOR> => gradient
+ * - <mxConstants.STYLE_GRADIENT_DIRECTION> => gradientDirection
+ * - <mxConstants.STYLE_OPACITY> => opacity
+ * - <mxConstants.STYLE_STROKECOLOR> => stroke
+ * - <mxConstants.STYLE_STROKEWIDTH> => strokewidth
+ * - <mxConstants.STYLE_SHADOW> => isShadow
+ * - <mxConstants.STYLE_DASHED> => isDashed
+ * - <mxConstants.STYLE_SPACING> => spacing
+ * - <mxConstants.STYLE_STARTSIZE> => startSize
+ * - <mxConstants.STYLE_ENDSIZE> => endSize
+ * - <mxConstants.STYLE_ROUNDED> => isRounded
+ * - <mxConstants.STYLE_STARTARROW> => startArrow
+ * - <mxConstants.STYLE_ENDARROW> => endArrow
+ * - <mxConstants.STYLE_ROTATION> => rotation
+ * - <mxConstants.STYLE_DIRECTION> => direction
+ *
+ * This keeps a reference to the <style>. If you need to keep a reference to
+ * the cell, you can override this method and store a local reference to
+ * state.cell or the <mxCellState> itself.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxShape.prototype.apply = function(state)
+{
+ var style = state.style;
+ this.style = style;
+
+ if (style != null)
+ {
+ this.fill = mxUtils.getValue(style, mxConstants.STYLE_FILLCOLOR, this.fill);
+ this.gradient = mxUtils.getValue(style, mxConstants.STYLE_GRADIENTCOLOR, this.gradient);
+ this.gradientDirection = mxUtils.getValue(style, mxConstants.STYLE_GRADIENT_DIRECTION, this.gradientDirection);
+ this.opacity = mxUtils.getValue(style, mxConstants.STYLE_OPACITY, this.opacity);
+ this.stroke = mxUtils.getValue(style, mxConstants.STYLE_STROKECOLOR, this.stroke);
+ this.strokewidth = mxUtils.getNumber(style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth);
+ this.isShadow = mxUtils.getValue(style, mxConstants.STYLE_SHADOW, this.isShadow);
+ this.isDashed = mxUtils.getValue(style, mxConstants.STYLE_DASHED, this.isDashed);
+ this.spacing = mxUtils.getValue(style, mxConstants.STYLE_SPACING, this.spacing);
+ this.startSize = mxUtils.getNumber(style, mxConstants.STYLE_STARTSIZE, this.startSize);
+ this.endSize = mxUtils.getNumber(style, mxConstants.STYLE_ENDSIZE, this.endSize);
+ this.isRounded = mxUtils.getValue(style, mxConstants.STYLE_ROUNDED, this.isRounded);
+ this.startArrow = mxUtils.getValue(style, mxConstants.STYLE_STARTARROW, this.startArrow);
+ this.endArrow = mxUtils.getValue(style, mxConstants.STYLE_ENDARROW, this.endArrow);
+ this.rotation = mxUtils.getValue(style, mxConstants.STYLE_ROTATION, this.rotation);
+ this.direction = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, this.direction);
+
+ if (this.fill == 'none')
+ {
+ this.fill = null;
+ }
+
+ if (this.gradient == 'none')
+ {
+ this.gradient = null;
+ }
+
+ if (this.stroke == 'none')
+ {
+ this.stroke = null;
+ }
+ }
+};
+
+/**
+ * Function: createSvgGroup
+ *
+ * Creates a SVG group element and adds the given shape as a child of the
+ * element. The child is stored in <innerNode> for later access.
+ */
+mxShape.prototype.createSvgGroup = function(shape)
+{
+ var g = document.createElementNS(mxConstants.NS_SVG, 'g');
+
+ // Creates the shape inside an svg group
+ this.innerNode = document.createElementNS(mxConstants.NS_SVG, shape);
+ this.configureSvgShape(this.innerNode);
+
+ // Avoids anti-aliasing for non-rounded rectangles with a
+ // strokewidth of 1 or more pixels
+ if (shape == 'rect' && this.strokewidth * this.scale >= 1 && !this.isRounded)
+ {
+ this.innerNode.setAttribute('shape-rendering', 'optimizeSpeed');
+ }
+
+ // Creates the shadow
+ this.shadowNode = this.createSvgShadow(this.innerNode);
+
+ if (this.shadowNode != null)
+ {
+ g.appendChild(this.shadowNode);
+ }
+
+ // Appends the main shape after the shadow
+ g.appendChild(this.innerNode);
+
+ return g;
+};
+
+/**
+ * Function: createSvgShadow
+ *
+ * Creates a clone of the given node and configures the node's color
+ * to use <mxConstants.SHADOWCOLOR>.
+ */
+mxShape.prototype.createSvgShadow = function(node)
+{
+ if (this.isShadow)
+ {
+ var shadow = node.cloneNode(true);
+ shadow.setAttribute('opacity', mxConstants.SHADOW_OPACITY);
+
+ if (this.fill != null && this.fill != mxConstants.NONE)
+ {
+ shadow.setAttribute('fill', mxConstants.SHADOWCOLOR);
+ }
+
+ if (this.stroke != null && this.stroke != mxConstants.NONE)
+ {
+ shadow.setAttribute('stroke', mxConstants.SHADOWCOLOR);
+ }
+
+ return shadow;
+ }
+
+ return null;
+};
+
+/**
+ * Function: configureHtmlShape
+ *
+ * Configures the specified HTML node by applying the current color,
+ * bounds, shadow, opacity etc.
+ */
+mxShape.prototype.configureHtmlShape = function(node)
+{
+ if (mxUtils.isVml(node))
+ {
+ this.configureVmlShape(node);
+ }
+ else
+ {
+ node.style.position = 'absolute';
+ node.style.overflow = 'hidden';
+ 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.ceil(this.strokewidth * this.scale) + 'px';
+ }
+ else
+ {
+ node.style.borderWidth = '0px';
+ }
+
+ color = this.fill;
+ node.style.background = '';
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ node.style.backgroundColor = color;
+ }
+ else if (this.points == null)
+ {
+ this.configureTransparentBackground(node);
+ }
+
+ if (this.opacity != null)
+ {
+ mxUtils.setOpacity(node, this.opacity);
+ }
+ }
+};
+
+/**
+ * Function: updateVmlFill
+ *
+ * Updates the given VML fill node.
+ */
+mxShape.prototype.updateVmlFill = function(node, c1, c2, dir, alpha)
+{
+ node.color = c1;
+
+ if (alpha != null && alpha != 100)
+ {
+ node.opacity = alpha + '%';
+
+ if (c2 != null)
+ {
+ // LATER: Set namespaced attribute without using setAttribute
+ // which is required for updating the value in IE8 standards.
+ node.setAttribute('o:opacity2', alpha + '%');
+ }
+ }
+
+ if (c2 != null)
+ {
+ node.type = 'gradient';
+ node.color2 = c2;
+ var angle = '180';
+
+ if (this.gradientDirection == mxConstants.DIRECTION_EAST)
+ {
+ angle = '270';
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_WEST)
+ {
+ angle = '90';
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_NORTH)
+ {
+ angle = '0';
+ }
+
+ node.angle = angle;
+ }
+};
+
+/**
+ * Function: updateVmlStrokeNode
+ *
+ * Creates the stroke node for VML.
+ */
+mxShape.prototype.updateVmlStrokeNode = function(parent)
+{
+ // Stroke node is always needed to specify defaults that match SVG output
+ if (this.strokeNode == null)
+ {
+ this.strokeNode = document.createElement('v:stroke');
+
+ // To math SVG defaults jointsyle miter and miterlimit 4
+ this.strokeNode.joinstyle = 'miter';
+ this.strokeNode.miterlimit = 4;
+
+ parent.appendChild(this.strokeNode);
+ }
+
+ if (this.opacity != null)
+ {
+ this.strokeNode.opacity = this.opacity + '%';
+ }
+
+ this.updateVmlDashStyle();
+};
+
+/**
+ * Function: updateVmlStrokeColor
+ *
+ * Updates the VML stroke color for the given node.
+ */
+mxShape.prototype.updateVmlStrokeColor = function(node)
+{
+ var color = this.stroke;
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ node.stroked = 'true';
+ node.strokecolor = color;
+ }
+ else
+ {
+ node.stroked = 'false';
+ }
+};
+
+/**
+ * Function: configureVmlShape
+ *
+ * Configures the specified VML node by applying the current color,
+ * bounds, shadow, opacity etc.
+ */
+mxShape.prototype.configureVmlShape = function(node)
+{
+ node.style.position = 'absolute';
+ this.updateVmlStrokeColor(node);
+ node.style.background = '';
+ var color = this.fill;
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ if (this.fillNode == null)
+ {
+ this.fillNode = document.createElement('v:fill');
+ node.appendChild(this.fillNode);
+ }
+
+ this.updateVmlFill(this.fillNode, color, this.gradient, this.gradientDirection, this.opacity);
+ }
+ else
+ {
+ node.filled = 'false';
+
+ if (this.points == null)
+ {
+ this.configureTransparentBackground(node);
+ }
+ }
+
+ this.updateVmlStrokeNode(node);
+
+ if (this.isShadow)
+ {
+ this.createVmlShadow(node);
+ }
+
+ // Fixes possible hang in IE when arcsize is set on non-rects
+ if (node.nodeName == 'roundrect')
+ {
+ // Workaround for occasional "member not found" error
+ try
+ {
+ var f = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+
+ if (this.style != null)
+ {
+ f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, f);
+ }
+
+ node.setAttribute('arcsize', String(f) + '%');
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+};
+
+/**
+ * Function: createVmlShadow
+ *
+ * Creates the VML shadow node.
+ */
+mxShape.prototype.createVmlShadow = function(node)
+{
+ // Adds a shadow only once per shape
+ if (this.shadowNode == null)
+ {
+ this.shadowNode = document.createElement('v:shadow');
+ this.shadowNode.on = 'true';
+ this.shadowNode.color = mxConstants.SHADOWCOLOR;
+ this.shadowNode.opacity = (mxConstants.SHADOW_OPACITY * 100) + '%';
+
+ this.shadowStrokeNode = document.createElement('v:stroke');
+ this.shadowNode.appendChild(this.shadowStrokeNode);
+
+ node.appendChild(this.shadowNode);
+ }
+};
+
+/**
+ * Function: configureTransparentBackground
+ *
+ * Hook to make the background of a shape transparent. This hook was added as
+ * a workaround for the "display non secure items" warning dialog in IE which
+ * appears if the background:url(transparent.gif) is used in the overlay pane
+ * of a diagram. Since only mxImageShapes currently exist in the overlay pane
+ * this function is only overridden in mxImageShape.
+ */
+mxShape.prototype.configureTransparentBackground = function(node)
+{
+ node.style.background = 'url(\'' + mxClient.imageBasePath + '/transparent.gif\')';
+};
+
+/**
+ * Function: configureSvgShape
+ *
+ * Configures the specified SVG node by applying the current color,
+ * bounds, shadow, opacity etc.
+ */
+mxShape.prototype.configureSvgShape = function(node)
+{
+ var color = this.stroke;
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ node.setAttribute('stroke', color);
+ }
+ else
+ {
+ node.setAttribute('stroke', 'none');
+ }
+
+ color = this.fill;
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ // Fetches a reference to a shared gradient
+ if (this.gradient != null)
+ {
+ var id = this.getGradientId(color, this.gradient);
+
+ if (this.gradientNode != null && this.gradientNode.getAttribute('id') != id)
+ {
+ this.gradientNode = null;
+ node.setAttribute('fill', '');
+ }
+
+ if (this.gradientNode == null)
+ {
+ this.gradientNode = this.createSvgGradient(id,
+ color, this.gradient, node);
+ node.setAttribute('fill', 'url(#'+id+')');
+ }
+ }
+ else
+ {
+ // TODO: Remove gradient from document if no longer shared
+ this.gradientNode = null;
+ node.setAttribute('fill', color);
+ }
+ }
+ else
+ {
+ node.setAttribute('fill', 'none');
+ }
+
+ if (this.opacity != null)
+ {
+ // Improves opacity performance in Firefox
+ node.setAttribute('fill-opacity', this.opacity / 100);
+ node.setAttribute('stroke-opacity', this.opacity / 100);
+ }
+};
+
+/**
+ * Function: getGradientId
+ *
+ * Creates a unique ID for the gradient of this shape.
+ */
+mxShape.prototype.getGradientId = function(start, end)
+{
+ // 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();
+ end = end.toLowerCase();
+
+ var dir = null;
+
+ if (this.gradientDirection == null ||
+ this.gradientDirection == mxConstants.DIRECTION_SOUTH)
+ {
+ dir = 'south';
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_EAST)
+ {
+ dir = 'east';
+ }
+ else
+ {
+ var tmp = start;
+ start = end;
+ end = tmp;
+
+ if (this.gradientDirection == mxConstants.DIRECTION_NORTH)
+ {
+ dir = 'south';
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_WEST)
+ {
+ dir = 'east';
+ }
+ }
+
+ return 'mx-gradient-'+start+'-'+end+'-'+dir;
+};
+
+/**
+ * Function: createSvgPipe
+ *
+ * Creates an invisible path which is used to increase the hit detection for
+ * edges in SVG.
+ */
+mxShape.prototype.createSvgPipe = function(id, start, end, node)
+{
+ var pipe = document.createElementNS(mxConstants.NS_SVG, 'path');
+ pipe.setAttribute('pointer-events', 'stroke');
+ pipe.setAttribute('fill', 'none');
+ pipe.setAttribute('visibility', 'hidden');
+ // 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 pipe.
+ pipe.setAttribute('stroke', (mxClient.IS_OP) ? 'none' : 'white');
+
+ return pipe;
+};
+
+/**
+ * Function: createSvgGradient
+ *
+ * Creates a gradient object for SVG using the specified startcolor,
+ * endcolor and opacity.
+ */
+mxShape.prototype.createSvgGradient = function(id, start, end, node)
+{
+ var gradient = this.insertGradientNode;
+
+ if (gradient == null)
+ {
+ gradient = document.createElementNS(mxConstants.NS_SVG, 'linearGradient');
+ gradient.setAttribute('id', id);
+ gradient.setAttribute('x1', '0%');
+ gradient.setAttribute('y1', '0%');
+ gradient.setAttribute('x2', '0%');
+ gradient.setAttribute('y2', '0%');
+
+ if (this.gradientDirection == null ||
+ this.gradientDirection == mxConstants.DIRECTION_SOUTH)
+ {
+ gradient.setAttribute('y2', '100%');
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_EAST)
+ {
+ gradient.setAttribute('x2', '100%');
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_NORTH)
+ {
+ gradient.setAttribute('y1', '100%');
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_WEST)
+ {
+ gradient.setAttribute('x1', '100%');
+ }
+
+ var stop = document.createElementNS(mxConstants.NS_SVG, 'stop');
+ stop.setAttribute('offset', '0%');
+ stop.setAttribute('style', 'stop-color:'+start);
+ gradient.appendChild(stop);
+
+ stop = document.createElementNS(mxConstants.NS_SVG, 'stop');
+ stop.setAttribute('offset', '100%');
+ stop.setAttribute('style', 'stop-color:'+end);
+ gradient.appendChild(stop);
+ }
+
+ // Inserted later when the owner SVG element is known
+ this.insertGradientNode = gradient;
+
+ return gradient;
+};
+
+/**
+ * Function: createPoints
+ *
+ * Creates a path expression using the specified commands for this.points.
+ * If <isRounded> is true, then the path contains curves for the corners.
+ */
+mxShape.prototype.createPoints = function(moveCmd, lineCmd, curveCmd, isRelative)
+{
+ var offsetX = (isRelative) ? this.bounds.x : 0;
+ var offsetY = (isRelative) ? this.bounds.y : 0;
+
+ // Workaround for crisp shape-rendering in IE9
+ var crisp = (this.crisp && this.dialect == mxConstants.DIALECT_SVG && mxClient.IS_IE) ? 0.5 : 0;
+
+ if (isNaN(this.points[0].x) || isNaN(this.points[0].y))
+ {
+ return null;
+ }
+
+ var size = mxConstants.LINE_ARCSIZE * this.scale;
+ var p0 = this.points[0];
+
+ if (this.startOffset != null)
+ {
+ p0 = p0.clone();
+ p0.x += this.startOffset.x;
+ p0.y += this.startOffset.y;
+ }
+
+ var points = moveCmd + ' ' + (Math.round(p0.x - offsetX) + crisp) + ' ' +
+ (Math.round(p0.y - offsetY) + crisp) + ' ';
+
+ for (var i = 1; i < this.points.length; i++)
+ {
+ p0 = this.points[i - 1];
+ var pt = this.points[i];
+
+ if (isNaN(pt.x) || isNaN(pt.y))
+ {
+ return null;
+ }
+
+ if (i == this.points.length - 1 && this.endOffset != null)
+ {
+ pt = pt.clone();
+ pt.x += this.endOffset.x;
+ pt.y += this.endOffset.y;
+ }
+
+ var dx = p0.x - pt.x;
+ var dy = p0.y - pt.y;
+
+ if ((this.isRounded && i < this.points.length - 1) &&
+ (dx != 0 || dy != 0) && this.scale > 0.3)
+ {
+ // 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(size, dist / 2) / dist;
+ var ny1 = dy * Math.min(size, dist / 2) / dist;
+ points += lineCmd + ' ' + (Math.round(pt.x + nx1 - offsetX) + crisp) + ' ' +
+ (Math.round(pt.y + ny1 - offsetY) + crisp) + ' ';
+
+ // 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 pe = this.points[i+1];
+ dx = pe.x - pt.x;
+ dy = pe.y - pt.y;
+
+ dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+
+ if (dist != 0)
+ {
+ var nx2 = dx * Math.min(size, dist / 2) / dist;
+ var ny2 = dy * Math.min(size, dist / 2) / dist;
+
+ points += curveCmd + ' ' + Math.round(pt.x - offsetX) + ' '+
+ Math.round(pt.y - offsetY) + ' ' + Math.round(pt.x - offsetX) + ',' +
+ Math.round(pt.y - offsetY) + ' ' + (Math.round(pt.x + nx2 - offsetX) + crisp) + ' ' +
+ (Math.round(pt.y + ny2 - offsetY) + crisp) + ' ';
+ }
+ }
+ else
+ {
+ points += lineCmd + ' ' + (Math.round(pt.x - offsetX) + crisp) + ' ' + (Math.round(pt.y - offsetY) + crisp) + ' ';
+ }
+ }
+
+ return points;
+};
+
+/**
+ * Function: updateHtmlShape
+ *
+ * Updates the bounds or points of the specified HTML node and
+ * updates the inner children to reflect the changes.
+ */
+mxShape.prototype.updateHtmlShape = function(node)
+{
+ if (node != null)
+ {
+ if (mxUtils.isVml(node))
+ {
+ this.updateVmlShape(node);
+ }
+ else
+ {
+ var sw = Math.ceil(this.strokewidth * this.scale);
+ node.style.borderWidth = Math.max(1, sw) + 'px';
+
+ if (this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+ !isNaN(this.bounds.width) && !isNaN(this.bounds.height))
+ {
+ 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';
+
+ if (this.bounds.width == 0 || this.bounds.height == 0)
+ {
+ node.style.visibility = 'hidden';
+ }
+ else
+ {
+ node.style.visibility = 'visible';
+ }
+ }
+ }
+
+ if (this.points != null && this.bounds != null && !mxUtils.isVml(node))
+ {
+ if (this.divContainer == null)
+ {
+ this.divContainer = node;
+ }
+
+ while (this.divContainer.firstChild != null)
+ {
+ mxEvent.release(this.divContainer.firstChild);
+ this.divContainer.removeChild(this.divContainer.firstChild);
+ }
+
+ node.style.borderStyle = '';
+ node.style.background = '';
+
+ if (this.points.length == 2)
+ {
+ var p0 = this.points[0];
+ var pe = this.points[1];
+
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+
+ if (dx == 0 || dy == 0)
+ {
+ node.style.borderStyle = 'solid';
+ }
+ else
+ {
+ node.style.width = Math.round(this.bounds.width + 1) + 'px';
+ node.style.height = Math.round(this.bounds.height + 1) + 'px';
+
+ var length = Math.sqrt(dx * dx + dy * dy);
+ var dotCount = 1 + (length / (8 * this.scale));
+
+ var nx = dx / dotCount;
+ var ny = dy / dotCount;
+ var x = p0.x - this.bounds.x;
+ var y = p0.y - this.bounds.y;
+
+ for (var i = 0; i < dotCount; i++)
+ {
+ var tmp = document.createElement('DIV');
+
+ tmp.style.position = 'absolute';
+ tmp.style.overflow = 'hidden';
+
+ tmp.style.left = Math.round(x) + 'px';
+ tmp.style.top = Math.round(y) + 'px';
+ tmp.style.width = Math.max(1, 2 * this.scale) + 'px';
+ tmp.style.height = Math.max(1, 2 * this.scale) + 'px';
+
+ tmp.style.backgroundColor = this.stroke;
+ this.divContainer.appendChild(tmp);
+
+ x += nx;
+ y += ny;
+ }
+ }
+ }
+ else if (this.points.length == 3)
+ {
+ var mid = this.points[1];
+
+ var n = '0';
+ var s = '1';
+ var w = '0';
+ var e = '1';
+
+ if (mid.x == this.bounds.x)
+ {
+ e = '0';
+ w = '1';
+ }
+
+ if (mid.y == this.bounds.y)
+ {
+ n = '1';
+ s = '0';
+ }
+
+ node.style.borderStyle = 'solid';
+ node.style.borderWidth = n + ' ' + e + ' ' + s + ' ' + w + 'px';
+ }
+ else
+ {
+ node.style.width = Math.round(this.bounds.width + 1) + 'px';
+ node.style.height = Math.round(this.bounds.height + 1) + 'px';
+ var last = this.points[0];
+
+ for (var i = 1; i < this.points.length; i++)
+ {
+ var next = this.points[i];
+
+ // TODO: Use one div for multiple lines
+ var tmp = document.createElement('DIV');
+
+ tmp.style.position = 'absolute';
+ tmp.style.overflow = 'hidden';
+
+ tmp.style.borderColor = this.stroke;
+ tmp.style.borderStyle = 'solid';
+ tmp.style.borderWidth = '1 0 0 1px';
+
+ var x = Math.min(next.x, last.x) - this.bounds.x;
+ var y = Math.min(next.y, last.y) - this.bounds.y;
+ var w = Math.max(1, Math.abs(next.x - last.x));
+ var h = Math.max(1, Math.abs(next.y - last.y));
+
+ tmp.style.left = x + 'px';
+ tmp.style.top = y + 'px';
+ tmp.style.width = w + 'px';
+ tmp.style.height = h + 'px';
+
+ this.divContainer.appendChild(tmp);
+ last = next;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: updateVmlDashStyle
+ *
+ * Updates the dashstyle in the stroke node.
+ */
+mxShape.prototype.updateVmlDashStyle = function()
+{
+ if (this.isDashed)
+ {
+ if (this.strokeNode.dashstyle != 'dash')
+ {
+ this.strokeNode.dashstyle = 'dash';
+ }
+ }
+ else if (this.strokeNode.dashstyle != 'solid')
+ {
+ this.strokeNode.dashstyle = 'solid';
+ }
+};
+
+/**
+ * Function: updateVmlShape
+ *
+ * Updates the bounds or points of the specified VML node and
+ * updates the inner children to reflect the changes.
+ */
+mxShape.prototype.updateVmlShape = function(node)
+{
+ node.strokeweight = (this.strokewidth * this.scale) + 'px';
+
+ // Dash pattern needs updating as it depends on strokeweight in VML
+ if (this.strokeNode != null)
+ {
+ this.updateVmlDashStyle();
+ }
+
+ // Updates the offset of the shadow
+ if (this.shadowNode != null)
+ {
+ var dx = Math.round(mxConstants.SHADOW_OFFSET_X * this.scale);
+ var dy = Math.round(mxConstants.SHADOW_OFFSET_Y * this.scale);
+ this.shadowNode.offset = dx + 'px,' + dy + 'px';
+ }
+
+ if (this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+ !isNaN(this.bounds.width) && !isNaN(this.bounds.height))
+ {
+ var f = 1;
+
+ var w = Math.max(0, Math.round(this.bounds.width));
+ var h = Math.max(0, Math.round(this.bounds.height));
+
+ // Groups and shapes need a coordsize
+ if (this.points != null || node.nodeName == 'shape' || node.nodeName == 'group')
+ {
+ var tmp = (node.parentNode.nodeName == 'group') ? 1 : this.vmlScale;
+ node.coordsize = (w * tmp) + ',' + (h * tmp);
+ }
+ else if (node.parentNode.nodeName == 'group')
+ {
+ f = this.vmlScale;
+ }
+
+ // Only top-level nodes are non-relative and rotated
+ if (node.parentNode != this.node)
+ {
+ node.style.left = Math.round(this.bounds.x * f) + 'px';
+ node.style.top = Math.round(this.bounds.y * f) + 'px';
+
+ if (this.points == null)
+ {
+ if (this.rotation != null && this.rotation != 0)
+ {
+ node.style.rotation = this.rotation;
+ }
+ else if (node.style.rotation != null)
+ {
+ node.style.rotation = '';
+ }
+ }
+ }
+
+ node.style.width = (w * f) + 'px';
+ node.style.height = (h * f) + 'px';
+ }
+
+ if (this.points != null && node.nodeName != 'group')
+ {
+ if (node.nodeName == 'polyline' && node.points != null)
+ {
+ var points = '';
+
+ for (var i = 0; i < this.points.length; i++)
+ {
+ points += this.points[i].x + ',' + this.points[i].y + ' ';
+ }
+
+ node.points.value = points;
+
+ node.style.left = null;
+ node.style.top = null;
+ node.style.width = null;
+ node.style.height = null;
+ }
+ else if (this.bounds != null)
+ {
+ var points = this.createPoints('m', 'l', 'c', true);
+
+ // Smooth style for VML (experimental)
+ if (this.style != null && this.style[mxConstants.STYLE_SMOOTH])
+ {
+ var pts = this.points;
+ var n = pts.length;
+
+ if (n > 3)
+ {
+ var x0 = this.bounds.x;
+ var y0 = this.bounds.y;
+ points = 'm ' + Math.round(pts[0].x - x0) + ' ' + Math.round(pts[0].y - y0) + ' qb';
+
+ for (var i = 1; i < n - 1; i++)
+ {
+ points += ' ' + Math.round(pts[i].x - x0) + ' ' + Math.round(pts[i].y - y0);
+ }
+
+ points += ' nf l ' + Math.round(pts[n - 1].x - x0) + ' ' + Math.round(pts[n - 1].y - y0);
+ }
+ }
+
+ node.path = points + ' e';
+ }
+ }
+};
+
+/**
+ * Function: updateSvgBounds
+ *
+ * Updates the bounds of the given node using <bounds>.
+ */
+mxShape.prototype.updateSvgBounds = function(node)
+{
+ var w = this.bounds.width;
+ var h = this.bounds.height;
+
+ if (this.isRounded && !(this.crisp && mxClient.IS_IE))
+ {
+ node.setAttribute('x', this.bounds.x);
+ node.setAttribute('y', this.bounds.y);
+ node.setAttribute('width', w);
+ node.setAttribute('height', h);
+ }
+ else
+ {
+ // Workaround for crisp shape-rendering in IE9
+ var dd = (this.crisp && mxClient.IS_IE) ? 0.5 : 0;
+ node.setAttribute('x', Math.round(this.bounds.x) + dd);
+ node.setAttribute('y', Math.round(this.bounds.y) + dd);
+
+ w = Math.round(w);
+ h = Math.round(h);
+
+ node.setAttribute('width', w);
+ node.setAttribute('height', h);
+ }
+
+ if (this.isRounded)
+ {
+ var f = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+
+ if (this.style != null)
+ {
+ f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, f) / 100;
+ }
+
+ var r = Math.min(w * f, h * f);
+ node.setAttribute('rx', r);
+ node.setAttribute('ry', r);
+ }
+
+ this.updateSvgTransform(node, node == this.shadowNode);
+};
+
+/**
+ * Function: updateSvgPath
+ *
+ * Updates the path of the given node using <points>.
+ */
+mxShape.prototype.updateSvgPath = function(node)
+{
+ var d = this.createPoints('M', 'L', 'C', false);
+
+ if (d != null)
+ {
+ node.setAttribute('d', d);
+
+ // Smooth style for SVG (experimental)
+ if (this.style != null && this.style[mxConstants.STYLE_SMOOTH])
+ {
+ var pts = this.points;
+ var n = pts.length;
+
+ if (n > 3)
+ {
+ var points = 'M '+pts[0].x+' '+pts[0].y+' ';
+ points += ' Q '+pts[1].x + ' ' + pts[1].y + ' ' +
+ ' '+pts[2].x + ' ' + pts[2].y;
+
+ for (var i = 3; i < n; i++)
+ {
+ points += ' T ' + pts[i].x + ' ' + pts[i].y;
+ }
+
+ node.setAttribute('d', points);
+ }
+ }
+
+ node.removeAttribute('x');
+ node.removeAttribute('y');
+ node.removeAttribute('width');
+ node.removeAttribute('height');
+ }
+};
+
+/**
+ * Function: updateSvgScale
+ *
+ * Updates the properties of the given node that depend on the scale and checks
+ * the crisp rendering attribute.
+ */
+mxShape.prototype.updateSvgScale = function(node)
+{
+ node.setAttribute('stroke-width', Math.round(Math.max(1, this.strokewidth * this.scale)));
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ node.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+
+ if (this.crisp && (this.roundedCrispSvg || this.isRounded != true) &&
+ (this.rotation == null || this.rotation == 0))
+ {
+ node.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ node.removeAttribute('shape-rendering');
+ }
+};
+
+/**
+ * Function: updateSvgShape
+ *
+ * Updates the bounds or points of the specified SVG node and
+ * updates the inner children to reflect the changes.
+ */
+mxShape.prototype.updateSvgShape = function(node)
+{
+ if (this.points != null && this.points[0] != null)
+ {
+ this.updateSvgPath(node);
+ }
+ else if (this.bounds != null)
+ {
+ this.updateSvgBounds(node);
+ }
+
+ this.updateSvgScale(node);
+};
+
+/**
+ * Function: getSvgShadowTransform
+ *
+ * Returns the current transformation for SVG shadows.
+ */
+mxShape.prototype.getSvgShadowTransform = function(node, shadow)
+{
+ var dx = mxConstants.SHADOW_OFFSET_X * this.scale;
+ var dy = mxConstants.SHADOW_OFFSET_Y * this.scale;
+
+ return 'translate(' + dx + ' ' + dy + ')';
+};
+
+/**
+ * Function: updateSvgTransform
+ *
+ * Updates the tranform of the given node.
+ */
+mxShape.prototype.updateSvgTransform = function(node, shadow)
+{
+ var st = (shadow) ? this.getSvgShadowTransform() : '';
+
+ if (this.rotation != null && this.rotation != 0)
+ {
+ var cx = this.bounds.x + this.bounds.width / 2;
+ var cy = this.bounds.y + this.bounds.height / 2;
+ node.setAttribute('transform', 'rotate(' + this.rotation + ',' + cx + ',' + cy + ') ' + st);
+ }
+ else
+ {
+ if (shadow)
+ {
+ node.setAttribute('transform', st);
+ }
+ else
+ {
+ node.removeAttribute('transform');
+ }
+ }
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Reconfigures this shape. This will update the colors etc in
+ * addition to the bounds or points.
+ */
+mxShape.prototype.reconfigure = function()
+{
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ if (this.innerNode != null)
+ {
+ this.configureSvgShape(this.innerNode);
+ }
+ else
+ {
+ this.configureSvgShape(this.node);
+ }
+
+ if (this.insertGradientNode != null)
+ {
+ this.insertGradient(this.insertGradientNode);
+ this.insertGradientNode = null;
+ }
+ }
+ else if (mxUtils.isVml(this.node))
+ {
+ this.node.style.visibility = 'hidden';
+ this.configureVmlShape(this.node);
+ this.node.style.visibility = 'visible';
+ }
+ else
+ {
+ this.node.style.visibility = 'hidden';
+ this.configureHtmlShape(this.node);
+ this.node.style.visibility = 'visible';
+ }
+};
+
+/**
+ * Function: redraw
+ *
+ * Invokes <redrawSvg>, <redrawVml> or <redrawHtml> depending on the
+ * dialect of the shape.
+ */
+mxShape.prototype.redraw = function()
+{
+ this.updateBoundingBox();
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.redrawSvg();
+ }
+ else if (mxUtils.isVml(this.node))
+ {
+ this.node.style.visibility = 'hidden';
+ this.redrawVml();
+ this.node.style.visibility = 'visible';
+ }
+ else
+ {
+ this.redrawHtml();
+ }
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using <createBoundingBox> and
+ * <augmentBoundingBox> and stores the result in <boundingBox>.
+ */
+mxShape.prototype.updateBoundingBox = function()
+{
+ if (this.bounds != null)
+ {
+ var bbox = this.createBoundingBox();
+ this.augmentBoundingBox(bbox);
+
+ var rot = Number(mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, 0));
+
+ if (rot != 0)
+ {
+ bbox = mxUtils.getBoundingBox(bbox, rot);
+ }
+
+ bbox.x = Math.floor(bbox.x);
+ bbox.y = Math.floor(bbox.y);
+ // TODO: Fix rounding errors
+ bbox.width = Math.ceil(bbox.width);
+ bbox.height = Math.ceil(bbox.height);
+
+ this.boundingBox = bbox;
+ }
+};
+
+/**
+ * Function: createBoundingBox
+ *
+ * Returns a new rectangle that represents the bounding box of the bare shape
+ * with no shadows or strokewidths.
+ */
+mxShape.prototype.createBoundingBox = function()
+{
+ return this.bounds.clone();
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the strokewidth and shadow offsets.
+ */
+mxShape.prototype.augmentBoundingBox = function(bbox)
+{
+ if (this.isShadow)
+ {
+ bbox.width += Math.ceil(mxConstants.SHADOW_OFFSET_X * this.scale);
+ bbox.height += Math.ceil(mxConstants.SHADOW_OFFSET_Y * this.scale);
+ }
+
+ // Adds strokeWidth
+ var sw = Math.ceil(this.strokewidth * this.scale);
+ bbox.grow(Math.ceil(sw / 2));
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Redraws this SVG shape by invoking <updateSvgShape> on this.node,
+ * this.innerNode and this.shadowNode.
+ */
+mxShape.prototype.redrawSvg = function()
+{
+ if (this.innerNode != null)
+ {
+ this.updateSvgShape(this.innerNode);
+
+ if (this.shadowNode != null)
+ {
+ this.updateSvgShape(this.shadowNode);
+ }
+ }
+ else
+ {
+ this.updateSvgShape(this.node);
+
+ // Updates the transform of the shadow
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform());
+ }
+ }
+
+ this.updateSvgGlassPane();
+};
+
+/**
+ * Function: updateVmlGlassPane
+ *
+ * Draws the glass overlay if mxConstants.STYLE_GLASS is 1.
+ */
+mxShape.prototype.updateVmlGlassPane = function()
+{
+ // Currently only used in mxLabel. Most shapes would have to be changed to use
+ // a group node in VML which might affect performance for glass-less cells.
+ if (this.bounds != null && this.node.nodeName == 'group' && this.style != null &&
+ mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, 0) == 1)
+ {
+ // Glass overlay
+ if (this.node.glassOverlay == null)
+ {
+ // Creates glass overlay
+ this.node.glassOverlay = document.createElement('v:shape');
+ this.node.glassOverlay.setAttribute('filled', 'true');
+ this.node.glassOverlay.setAttribute('fillcolor', 'white');
+ this.node.glassOverlay.setAttribute('stroked', 'false');
+
+ var fillNode = document.createElement('v:fill');
+ fillNode.setAttribute('type', 'gradient');
+ fillNode.setAttribute('color', 'white');
+ fillNode.setAttribute('color2', 'white');
+ fillNode.setAttribute('opacity', '90%');
+ fillNode.setAttribute('o:opacity2', '15%');
+ fillNode.setAttribute('angle', '180');
+
+ this.node.glassOverlay.appendChild(fillNode);
+ this.node.appendChild(this.node.glassOverlay);
+ }
+
+ var size = 0.4;
+
+ // TODO: Mask with rectangle or rounded rectangle of label
+ var b = this.bounds;
+ var sw = Math.ceil(this.strokewidth * this.scale / 2 + 1);
+ var d = 'm ' + (-sw) + ' ' + (-sw) + ' l ' + (-sw) + ' ' + Math.round(b.height * size) +
+ ' c ' + Math.round(b.width * 0.3) + ' ' + Math.round(b.height * 0.6) +
+ ' ' + Math.round(b.width * 0.7) + ' ' + Math.round(b.height * 0.6) +
+ ' ' + Math.round(b.width + sw) + ' ' + Math.round(b.height * size) +
+ ' l '+Math.round(b.width + sw)+' ' + (-sw) + ' x e';
+ this.node.glassOverlay.style.position = 'absolute';
+ this.node.glassOverlay.style.width = b.width + 'px';
+ this.node.glassOverlay.style.height = b.height + 'px';
+ this.node.glassOverlay.setAttribute('coordsize',
+ Math.round(this.bounds.width) + ',' +
+ Math.round(this.bounds.height));
+ this.node.glassOverlay.setAttribute('path', d);
+ }
+ else if (this.node.glassOverlay != null)
+ {
+ this.node.glassOverlay.parentNode.removeChild(this.node.glassOverlay);
+ this.node.glassOverlay = null;
+ }
+};
+
+/**
+ * Function: updateSvgGlassPane
+ *
+ * Draws the glass overlay if mxConstants.STYLE_GLASS is 1.
+ */
+mxShape.prototype.updateSvgGlassPane = function()
+{
+ if (this.node.nodeName == 'g' && this.style != null &&
+ mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, 0) == 1)
+ {
+ // Glass overlay
+ if (this.node.glassOverlay == null)
+ {
+ // Glass overlay gradient
+ if (this.node.ownerSVGElement.glassGradient == null)
+ {
+ // Creates glass overlay gradient
+ var glassGradient = document.createElementNS(mxConstants.NS_SVG, 'linearGradient');
+ glassGradient.setAttribute('x1', '0%');
+ glassGradient.setAttribute('y1', '0%');
+ glassGradient.setAttribute('x2', '0%');
+ glassGradient.setAttribute('y2', '100%');
+
+ var stop1 = document.createElementNS(mxConstants.NS_SVG, 'stop');
+ stop1.setAttribute('offset', '0%');
+ stop1.setAttribute('style', 'stop-color:#ffffff;stop-opacity:0.9');
+ glassGradient.appendChild(stop1);
+
+ var stop2 = document.createElementNS(mxConstants.NS_SVG, 'stop');
+ stop2.setAttribute('offset', '100%');
+ stop2.setAttribute('style', 'stop-color:#ffffff;stop-opacity:0.1');
+ glassGradient.appendChild(stop2);
+
+ // Finds a unique ID for the gradient
+ var prefix = 'mx-glass-gradient-';
+ var counter = 0;
+
+ while (document.getElementById(prefix+counter) != null)
+ {
+ counter++;
+ }
+
+ glassGradient.setAttribute('id', prefix+counter);
+ this.node.ownerSVGElement.appendChild(glassGradient);
+ this.node.ownerSVGElement.glassGradient = glassGradient;
+ }
+
+ // Creates glass overlay
+ this.node.glassOverlay = document.createElementNS(mxConstants.NS_SVG, 'path');
+ // LATER: Not sure what the behaviour is for mutiple SVG elements in page.
+ // Probably its possible that this points to an element in another SVG
+ // node which when removed will result in an undefined background.
+ var id = this.node.ownerSVGElement.glassGradient.getAttribute('id');
+ this.node.glassOverlay.setAttribute('style', 'fill:url(#'+id+');');
+ this.node.appendChild(this.node.glassOverlay);
+ }
+
+ var size = 0.4;
+
+ // TODO: Mask with rectangle or rounded rectangle of label
+ var b = this.bounds;
+ var sw = Math.ceil(this.strokewidth * this.scale / 2);
+ var d = 'm ' + (b.x - sw) + ',' + (b.y - sw) +
+ ' L ' + (b.x - sw) + ',' + (b.y + b.height * size) +
+ ' Q '+ (b.x + b.width * 0.5) + ',' + (b.y + b.height * 0.7) + ' '+
+ (b.x + b.width + sw) + ',' + (b.y + b.height * size) +
+ ' L ' + (b.x + b.width + sw) + ',' + (b.y - sw) + ' z';
+ this.node.glassOverlay.setAttribute('d', d);
+ }
+ else if (this.node.glassOverlay != null)
+ {
+ this.node.glassOverlay.parentNode.removeChild(this.node.glassOverlay);
+ this.node.glassOverlay = null;
+ }
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Redraws this VML shape by invoking <updateVmlShape> on this.node.
+ */
+mxShape.prototype.redrawVml = function()
+{
+ this.node.style.visibility = 'hidden';
+ this.updateVmlShape(this.node);
+ this.updateVmlGlassPane();
+ this.node.style.visibility = 'visible';
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Redraws this HTML shape by invoking <updateHtmlShape> on this.node.
+ */
+mxShape.prototype.redrawHtml = function()
+{
+ this.updateHtmlShape(this.node);
+};
+
+/**
+ * Function: getRotation
+ *
+ * Returns the current rotation including direction.
+ */
+mxShape.prototype.getRotation = function()
+{
+ var rot = this.rotation || 0;
+
+ // Default direction is east (ignored if rotation exists)
+ if (this.direction != null)
+ {
+ if (this.direction == 'north')
+ {
+ rot += 270;
+ }
+ else if (this.direction == 'west')
+ {
+ rot += 180;
+ }
+ else if (this.direction == 'south')
+ {
+ rot += 90;
+ }
+ }
+
+ return rot;
+};
+
+/**
+ * Function: createPath
+ *
+ * Creates an <mxPath> for the specified format and origin. The path object is
+ * then passed to <redrawPath> and <mxPath.getPath> is returned.
+ */
+mxShape.prototype.createPath = function(arg)
+{
+ var x = this.bounds.x;
+ var y = this.bounds.y;
+ var w = this.bounds.width;
+ var h = this.bounds.height;
+ var dx = 0;
+ var dy = 0;
+
+ // Inverts bounds for stencils which are rotated 90 or 270 degrees
+ if (this.direction == 'north' || this.direction == 'south')
+ {
+ dx = (w - h) / 2;
+ dy = (h - w) / 2;
+ x += dx;
+ y += dy;
+ var tmp = w;
+ w = h;
+ h = tmp;
+ }
+
+ var rotation = this.getRotation();
+ var path = null;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ path = new mxPath('svg');
+ path.setTranslate(x, y);
+
+ // Adds rotation as a separate transform
+ if (rotation != 0)
+ {
+ var cx = this.bounds.getCenterX();
+ var cy = this.bounds.getCenterY();
+ var transform = 'rotate(' + rotation + ' ' + cx + ' ' + cy + ')';
+
+ if (this.innerNode != null)
+ {
+ this.innerNode.setAttribute('transform', transform);
+ }
+
+ if (this.foreground != null)
+ {
+ this.foreground.setAttribute('transform', transform);
+ }
+
+ // Shadow needs different transform so that it ends up on the correct side
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform() + ' ' + transform);
+ }
+ }
+ }
+ else
+ {
+ path = new mxPath('vml');
+ path.setTranslate(dx, -dx);
+ path.scale = this.vmlScale;
+
+ if (rotation != 0)
+ {
+ this.node.style.rotation = rotation;
+ }
+ }
+
+ this.redrawPath(path, x, y, w, h, arg);
+
+ return path.getPath();
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This implementation is empty. See
+ * <mxActor> and <mxCylinder> for implementations.
+ */
+mxShape.prototype.redrawPath = function(path, x, y, w, h)
+{
+ // do nothing
+};
diff --git a/src/js/shape/mxStencil.js b/src/js/shape/mxStencil.js
new file mode 100644
index 0000000..d0e1a63
--- /dev/null
+++ b/src/js/shape/mxStencil.js
@@ -0,0 +1,1585 @@
+/**
+ * $Id: mxStencil.js,v 1.91 2012-07-16 10:22:44 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxStencil
+ *
+ * Implements a generic shape which is based on a XML node as a description.
+ * The node contains a background and a foreground node, which contain the
+ * definition to render the respective part of the shape. Note that the
+ * fill, stroke or fillstroke of the background is be the first statement
+ * of the foreground. This is because the content of the background node
+ * maybe used to not only render the shape itself, but also its shadow and
+ * other elements which do not require a fill, stroke or fillstroke.
+ *
+ * The shape uses a coordinate system with a width of 100 and a height of
+ * 100 by default. This can be changed by setting the w and h attribute of
+ * the shape element. The aspect attribute can be set to "variable" (default)
+ * or "fixed". If fixed is used, then the aspect which is defined via the w
+ * and h attribute is kept constant while the shape is scaled.
+ *
+ * The possible contents of the background and foreground elements are rect,
+ * ellipse, roundrect, text, image, include-shape or paths. A path element
+ * contains move, line, curve, quad, arc and close elements. The rect, ellipse
+ * and roundrect elements may be thought of as special path elements. All these
+ * path elements must be followed by either fill, stroke or fillstroke (note
+ * that text, image and include-shape or not path elements).
+ *
+ * The background element can be empty or contain at most one path element. It
+ * should not contain a text, image or include-shape element. If the background
+ * element is empty, then no shadow or glass effect will be rendered. If the
+ * background element is non-empty, then the corresponding fill, stroke or
+ * fillstroke should be the first element in the subsequent foreground element.
+ *
+ * The format of the XML is "a simplified HTML 5 Canvas". Each command changes
+ * the "current" state, so eg. a linecap, linejoin will be used for all
+ * subsequent line drawing, unless a save/restore appears, which saves/restores
+ * a state in a stack.
+ *
+ * The connections section contains the fixed connection points for a stencil.
+ * The perimeter attribute of the constraint element should have a value of 0
+ * or 1 (default), where 1 (true) specifies that the given point should be
+ * projected into the perimeter of the given shape.
+ *
+ * The x- and y-coordinates are typically between 0 and 1 and define the
+ * location of the connection point relative to the width and height of the
+ * shape.
+ *
+ * The dashpattern directive sets the current dashpattern. The format for the
+ * pattern attribute is a space-separated sequence of numbers, eg. 5 5 5 5,
+ * that specifies the lengths of alternating dashes and spaces in dashed lines.
+ * The dashpattern should be used together with the dashed directive to
+ * enabled/disable the dashpattern. The default dashpattern is 3 3.
+ *
+ * The strokewidth attribute defines a fixed strokewidth for the shape. It
+ * can contain a numeric value or the keyword "inherit", which means that the
+ * strokeWidth from the cell's style will be used and muliplied with the shape's
+ * scale. If numeric values are used, those are multiplied with the minimum
+ * scale used to render the stencil inside the shape's bounds.
+ *
+ * Constructor: mxStencilShape
+ *
+ * Constructs a new generic shape by setting <desc> to the given XML node and
+ * invoking <parseDescription> and <parseConstraints>.
+ *
+ * Parameters:
+ *
+ * desc - XML node that contains the stencil description.
+ */
+function mxStencil(desc)
+{
+ this.desc = desc;
+ this.parseDescription();
+ this.parseConstraints();
+};
+
+/**
+ * Variable: desc
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.desc = null;
+
+/**
+ * Variable: constraints
+ *
+ * Holds an array of <mxConnectionConstraints> as defined in the shape.
+ */
+mxStencil.prototype.constraints = null;
+
+/**
+ * Variable: aspect
+ *
+ * Holds the aspect of the shape. Default is 'auto'.
+ */
+mxStencil.prototype.aspect = null;
+
+/**
+ * Variable: w0
+ *
+ * Holds the width of the shape. Default is 100.
+ */
+mxStencil.prototype.w0 = null;
+
+/**
+ * Variable: h0
+ *
+ * Holds the height of the shape. Default is 100.
+ */
+mxStencil.prototype.h0 = null;
+
+/**
+ * Variable: bgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.bgNode = null;
+
+/**
+ * Variable: fgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.fgNode = null;
+
+/**
+ * Variable: strokewidth
+ *
+ * Holds the strokewidth direction from the description.
+ */
+mxStencil.prototype.strokewidth = null;
+
+/**
+ * Function: parseDescription
+ *
+ * Reads <w0>, <h0>, <aspect>, <bgNodes> and <fgNodes> from <desc>.
+ */
+mxStencil.prototype.parseDescription = function()
+{
+ // LATER: Preprocess nodes for faster painting
+ this.fgNode = this.desc.getElementsByTagName('foreground')[0];
+ this.bgNode = this.desc.getElementsByTagName('background')[0];
+ this.w0 = Number(this.desc.getAttribute('w') || 100);
+ this.h0 = Number(this.desc.getAttribute('h') || 100);
+
+ // Possible values for aspect are: variable and fixed where
+ // variable means fill the available space and fixed means
+ // use w0 and h0 to compute the aspect.
+ var aspect = this.desc.getAttribute('aspect');
+ this.aspect = (aspect != null) ? aspect : 'variable';
+
+ // Possible values for strokewidth are all numbers and "inherit"
+ // where the inherit means take the value from the style (ie. the
+ // user-defined stroke-width). Note that the strokewidth is scaled
+ // by the minimum scaling that is used to draw the shape (sx, sy).
+ var sw = this.desc.getAttribute('strokewidth');
+ this.strokewidth = (sw != null) ? sw : '1';
+};
+
+/**
+ * Function: parseConstraints
+ *
+ * Reads the constraints from <desc> into <constraints> using
+ * <parseConstraint>.
+ */
+mxStencil.prototype.parseConstraints = function()
+{
+ var conns = this.desc.getElementsByTagName('connections')[0];
+
+ if (conns != null)
+ {
+ var tmp = mxUtils.getChildNodes(conns);
+
+ if (tmp != null && tmp.length > 0)
+ {
+ this.constraints = [];
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ this.constraints.push(this.parseConstraint(tmp[i]));
+ }
+ }
+ }
+};
+
+/**
+ * Function: parseConstraint
+ *
+ * Parses the given XML node and returns its <mxConnectionConstraint>.
+ */
+mxStencil.prototype.parseConstraint = function(node)
+{
+ var x = Number(node.getAttribute('x'));
+ var y = Number(node.getAttribute('y'));
+ var perimeter = node.getAttribute('perimeter') == '1';
+
+ return new mxConnectionConstraint(new mxPoint(x, y), perimeter);
+};
+
+/**
+ * Function: evaluateAttribute
+ *
+ * Gets the attribute for the given name from the given node. If the attribute
+ * does not exist then the text content of the node is evaluated and if it is
+ * a function it is invoked with <state> as the only argument and the return
+ * value is used as the attribute value to be returned.
+ */
+mxStencil.prototype.evaluateAttribute = function(node, attribute, state)
+{
+ var result = node.getAttribute(attribute);
+
+ if (result == null)
+ {
+ var text = mxUtils.getTextContent(node);
+
+ if (text != null)
+ {
+ var funct = mxUtils.eval(text);
+
+ if (typeof(funct) == 'function')
+ {
+ result = funct(state);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: renderDom
+ *
+ * Updates the SVG or VML shape.
+ */
+mxStencil.prototype.renderDom = function(shape, bounds, parentNode, state)
+{
+ var vml = shape.dialect != mxConstants.DIALECT_SVG;
+ var vmlScale = (document.documentMode == 8) ? 1 : shape.vmlScale;
+ var rotation = shape.rotation || 0;
+ var inverse = false;
+
+ // New styles for shape flipping the stencil
+ var flipH = shape.style[mxConstants.STYLE_STENCIL_FLIPH];
+ var flipV = shape.style[mxConstants.STYLE_STENCIL_FLIPV];
+
+ if (flipH ? !flipV : flipV)
+ {
+ rotation *= -1;
+ }
+
+ // Default direction is east (ignored if rotation exists)
+ if (shape.direction != null)
+ {
+ if (shape.direction == 'north')
+ {
+ rotation += 270;
+ }
+ else if (shape.direction == 'west')
+ {
+ rotation += 180;
+ }
+ else if (shape.direction == 'south')
+ {
+ rotation += 90;
+ }
+
+ inverse = (shape.direction == 'north' || shape.direction == 'south');
+ }
+
+ if (flipH && flipV)
+ {
+ rotation += 180;
+ flipH = false;
+ flipV = false;
+ }
+
+ // SVG transform should be applied on all child shapes
+ var svgTransform = '';
+
+ // Implements direction style and vertical/horizontal flip
+ // via container transformation.
+ if (vml)
+ {
+ if (flipH)
+ {
+ parentNode.style.flip = 'x';
+ }
+ else if (flipV)
+ {
+ parentNode.style.flip = 'y';
+ }
+
+ if (rotation != 0)
+ {
+ parentNode.style.rotation = rotation;
+ }
+ }
+ else
+ {
+ if (flipH || flipV)
+ {
+ var sx = 1;
+ var sy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ if (flipH)
+ {
+ sx = -1;
+ dx = -bounds.width - 2 * bounds.x;
+ }
+
+ if (flipV)
+ {
+ sy = -1;
+ dy = -bounds.height - 2 * bounds.y;
+ }
+
+ svgTransform = 'scale(' + sx + ' ' + sy + ') translate(' + dx + ' ' + dy + ')';
+ }
+
+ // Adds rotation as a separate transform
+ if (rotation != 0)
+ {
+ var cx = bounds.getCenterX();
+ var cy = bounds.getCenterY();
+ svgTransform += ' rotate(' + rotation + ' ' + cx + ' ' + cy + ')';
+ }
+ }
+
+ var background = (state == null);
+
+ if (this.bgNode != null || this.fgNode != null)
+ {
+ var x0 = (vml && state == null) ? 0 : bounds.x;
+ var y0 = (vml && state == null) ? 0 : bounds.y;
+ var sx = bounds.width / this.w0;
+ var sy = bounds.height / this.h0;
+
+ // Stores current location inside path
+ this.lastMoveX = 0;
+ this.lastMoveY = 0;
+
+ if (inverse)
+ {
+ sy = bounds.width / this.h0;
+ sx = bounds.height / this.w0;
+
+ var delta = (bounds.width - bounds.height) / 2;
+
+ x0 += delta;
+ y0 -= delta;
+ }
+
+ if (this.aspect == 'fixed')
+ {
+ sy = Math.min(sx, sy);
+ sx = sy;
+
+ // Centers the shape inside the available space
+ if (inverse)
+ {
+ x0 += (bounds.height - this.w0 * sx) / 2;
+ y0 += (bounds.width - this.h0 * sy) / 2;
+ }
+ else
+ {
+ x0 += (bounds.width - this.w0 * sx) / 2;
+ y0 += (bounds.height - this.h0 * sy) / 2;
+ }
+ }
+
+ // Workaround to improve VML rendering precision.
+ if (vml)
+ {
+ sx *= vmlScale;
+ sy *= vmlScale;
+ x0 *= vmlScale;
+ y0 *= vmlScale;
+ }
+
+ var minScale = Math.min(sx, sy);
+
+ // Stack of states for save/restore ops
+ var stack = [];
+
+ var currentState = (state != null) ? state :
+ {
+ fillColorAssigned: false,
+ fill: shape.fill,
+ stroke: shape.stroke,
+ strokeWidth: (this.strokewidth == 'inherit') ?
+ Number(shape.strokewidth) * shape.scale :
+ Number(this.strokewidth) * minScale / ((vml) ? vmlScale : 1),
+ dashed: shape.isDashed,
+ dashpattern: [3, 3],
+ alpha: shape.opacity,
+ linejoin: 'miter',
+ fontColor: '#000000',
+ fontSize: mxConstants.DEFAULT_FONTSIZE,
+ fontFamily: mxConstants.DEFAULT_FONTFAMILY,
+ fontStyle: 0
+ };
+
+ var currentPath = null;
+ var currentPoints = null;
+
+ var configurePath = function(path, state)
+ {
+ var sw = Math.max(1, state.strokeWidth);
+
+ if (vml)
+ {
+ path.strokeweight = Math.round(sw) + 'px';
+
+ if (state.fill != null)
+ {
+ // Gradient in foregrounds not supported because special gradients
+ // with bounds must be created for each element in graphics-canvases
+ var gradient = (!state.fillColorAssigned) ? shape.gradient : null;
+ var fill = document.createElement('v:fill');
+ shape.updateVmlFill(fill, state.fill, gradient, shape.gradientDirection, state.alpha);
+ path.appendChild(fill);
+ }
+ else
+ {
+ path.filled = 'false';
+ }
+
+ if (state.stroke != null)
+ {
+ path.stroked = 'true';
+ path.strokecolor = state.stroke;
+ }
+ else
+ {
+ path.stroked = 'false';
+ }
+
+ path.style.position = 'absolute';
+ }
+ else
+ {
+ path.setAttribute('stroke-width', sw);
+
+ if (state.fill != null && state.fillColorAssigned)
+ {
+ path.setAttribute('fill', state.fill);
+ }
+
+ if (state.stroke != null)
+ {
+ path.setAttribute('stroke', state.stroke);
+ }
+ }
+ };
+
+ var addToPath = function(s)
+ {
+ if (currentPath != null && currentPoints != null)
+ {
+ currentPoints.push(s);
+ }
+ };
+
+ var round = function(value)
+ {
+ return (vml) ? Math.round(value) : value;
+ };
+
+ // Will be moved to a hook later for example to set text values
+ var renderNode = function(node)
+ {
+ var name = node.nodeName;
+
+ var fillOp = name == 'fill';
+ var strokeOp = name == 'stroke';
+ var fillStrokeOp = name == 'fillstroke';
+
+ if (name == 'save')
+ {
+ stack.push(currentState);
+ currentState = mxUtils.clone(currentState);
+ }
+ else if (name == 'restore')
+ {
+ currentState = stack.pop();
+ }
+ else if (name == 'path')
+ {
+ currentPoints = [];
+
+ if (vml)
+ {
+ currentPath = document.createElement('v:shape');
+ configurePath.call(this, currentPath, currentState);
+ var w = Math.round(bounds.width) * vmlScale;
+ var h = Math.round(bounds.height) * vmlScale;
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+ currentPath.coordsize = w + ',' + h;
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'path');
+ configurePath.call(this, currentPath, currentState);
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+
+ if (node.getAttribute('crisp') == '1')
+ {
+ currentPath.setAttribute('shape-rendering', 'crispEdges');
+ }
+ }
+
+ // Renders the elements inside the given path
+ var childNode = node.firstChild;
+
+ while (childNode != null)
+ {
+ if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ renderNode.call(this, childNode);
+ }
+
+ childNode = childNode.nextSibling;
+ }
+
+ // Ends the current path
+ if (vml)
+ {
+ addToPath('e');
+ currentPath.path = currentPoints.join('');
+ }
+ else
+ {
+ currentPath.setAttribute('d', currentPoints.join(''));
+ }
+ }
+ else if (name == 'move')
+ {
+ var op = (vml) ? 'm' : 'M';
+ this.lastMoveX = round(x0 + Number(node.getAttribute('x')) * sx);
+ this.lastMoveY = round(y0 + Number(node.getAttribute('y')) * sy);
+ addToPath(op + ' ' + this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ else if (name == 'line')
+ {
+ var op = (vml) ? 'l' : 'L';
+ this.lastMoveX = round(x0 + Number(node.getAttribute('x')) * sx);
+ this.lastMoveY = round(y0 + Number(node.getAttribute('y')) * sy);
+ addToPath(op + ' ' + this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ else if (name == 'quad')
+ {
+ if (vml)
+ {
+ var cpx0 = this.lastMoveX;
+ var cpy0 = this.lastMoveY;
+ var qpx1 = x0 + Number(node.getAttribute('x1')) * sx;
+ var qpy1 = y0 + Number(node.getAttribute('y1')) * sy;
+ var cpx3 = x0 + Number(node.getAttribute('x2')) * sx;
+ var cpy3 = y0 + Number(node.getAttribute('y2')) * sy;
+
+ 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);
+
+ addToPath('c ' + Math.round(cpx1) + ' ' + Math.round(cpy1) + ' ' +
+ Math.round(cpx2) + ' ' + Math.round(cpy2) + ' ' +
+ Math.round(cpx3) + ' ' + Math.round(cpy3));
+
+ this.lastMoveX = cpx3;
+ this.lastMoveY = cpy3;
+ }
+ else
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x2')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y2')) * sy;
+
+ addToPath('Q ' + (x0 + Number(node.getAttribute('x1')) * sx) + ' ' +
+ (y0 + Number(node.getAttribute('y1')) * sy) + ' ' +
+ this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ }
+ else if (name == 'curve')
+ {
+ var op = (vml) ? 'c' : 'C';
+ this.lastMoveX = round(x0 + Number(node.getAttribute('x3')) * sx);
+ this.lastMoveY = round(y0 + Number(node.getAttribute('y3')) * sy);
+
+ addToPath(op + ' ' + round(x0 + Number(node.getAttribute('x1')) * sx) + ' ' +
+ round(y0 + Number(node.getAttribute('y1')) * sy) + ' ' +
+ round(x0 + Number(node.getAttribute('x2')) * sx) + ' ' +
+ round(y0 + Number(node.getAttribute('y2')) * sy) + ' ' +
+ this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ else if (name == 'close')
+ {
+ addToPath((vml) ? 'x' : 'Z');
+ }
+ else if (name == 'rect' || name == 'roundrect')
+ {
+ var rounded = name == 'roundrect';
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var w = round(Number(node.getAttribute('w')) * sx);
+ var h = round(Number(node.getAttribute('h')) * sy);
+
+ var arcsize = node.getAttribute('arcsize');
+
+ if (arcsize == 0)
+ {
+ arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+ }
+
+ if (vml)
+ {
+ // LATER: Use HTML for non-rounded, gradientless rectangles
+ currentPath = document.createElement((rounded) ? 'v:roundrect' : 'v:rect');
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+
+ if (rounded)
+ {
+ currentPath.setAttribute('arcsize', String(arcsize) + '%');
+ }
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ currentPath.setAttribute('x', x);
+ currentPath.setAttribute('y', y);
+ currentPath.setAttribute('width', w);
+ currentPath.setAttribute('height', h);
+
+ if (rounded)
+ {
+ var factor = Number(arcsize) / 100;
+ var r = Math.min(w * factor, h * factor);
+ currentPath.setAttribute('rx', r);
+ currentPath.setAttribute('ry', r);
+ }
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+
+ if (node.getAttribute('crisp') == '1')
+ {
+ currentPath.setAttribute('shape-rendering', 'crispEdges');
+ }
+ }
+
+ configurePath.call(this, currentPath, currentState);
+ }
+ else if (name == 'ellipse')
+ {
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var w = round(Number(node.getAttribute('w')) * sx);
+ var h = round(Number(node.getAttribute('h')) * sy);
+
+ if (vml)
+ {
+ currentPath = document.createElement('v:arc');
+ currentPath.startangle = '0';
+ currentPath.endangle = '360';
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'ellipse');
+ currentPath.setAttribute('cx', x + w / 2);
+ currentPath.setAttribute('cy', y + h / 2);
+ currentPath.setAttribute('rx', w / 2);
+ currentPath.setAttribute('ry', h / 2);
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+ }
+
+ configurePath.call(this, currentPath, currentState);
+ }
+ else if (name == 'arc')
+ {
+ var r1 = Number(node.getAttribute('rx')) * sx;
+ var r2 = Number(node.getAttribute('ry')) * sy;
+ var angle = Number(node.getAttribute('x-axis-rotation'));
+ var largeArcFlag = Number(node.getAttribute('large-arc-flag'));
+ var sweepFlag = Number(node.getAttribute('sweep-flag'));
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+
+ if (vml)
+ {
+ var curves = mxUtils.arcToCurves(this.lastMoveX, this.lastMoveY, r1, r2, angle, largeArcFlag, sweepFlag, x, y);
+
+ for (var i = 0; i < curves.length; i += 6)
+ {
+ addToPath('c' + ' ' + Math.round(curves[i]) + ' ' + Math.round(curves[i + 1]) + ' ' +
+ Math.round(curves[i + 2]) + ' ' + Math.round(curves[i + 3]) + ' ' +
+ Math.round(curves[i + 4]) + ' ' + Math.round(curves[i + 5]));
+
+ this.lastMoveX = curves[i + 4];
+ this.lastMoveY = curves[i + 5];
+ }
+ }
+ else
+ {
+ addToPath('A ' + r1 + ',' + r2 + ' ' + angle + ' ' + largeArcFlag + ',' + sweepFlag + ' ' + x + ',' + y);
+ this.lastMoveX = x0 + x;
+ this.lastMoveY = y0 + y;
+ }
+ }
+ else if (name == 'image')
+ {
+ var src = this.evaluateAttribute(node, 'src', shape.state);
+
+ if (src != null)
+ {
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var w = round(Number(node.getAttribute('w')) * sx);
+ var h = round(Number(node.getAttribute('h')) * sy);
+
+ // TODO: _Not_ providing an aspect in the shapes format has the advantage
+ // of not needing a callback to adjust the image in VML. Since the shape
+ // developer can specify the aspect via width and height this should OK.
+ //var aspect = node.getAttribute('aspect') != '0';
+ var aspect = false;
+ var flipH = node.getAttribute('flipH') == '1';
+ var flipV = node.getAttribute('flipV') == '1';
+
+ if (vml)
+ {
+ currentPath = document.createElement('v:image');
+ currentPath.style.filter = 'alpha(opacity=' + currentState.alpha + ')';
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+ currentPath.src = src;
+
+ if (flipH && flipV)
+ {
+ currentPath.style.rotation = '180';
+ }
+ else if (flipH)
+ {
+ currentPath.style.flip = 'x';
+ }
+ else if (flipV)
+ {
+ currentPath.style.flip = 'y';
+ }
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'image');
+ currentPath.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
+ currentPath.setAttribute('opacity', currentState.alpha / 100);
+ currentPath.setAttribute('x', x);
+ currentPath.setAttribute('y', y);
+ currentPath.setAttribute('width', w);
+ currentPath.setAttribute('height', h);
+
+ if (!aspect)
+ {
+ currentPath.setAttribute('preserveAspectRatio', 'none');
+ }
+
+ if (flipH || flipV)
+ {
+ var scx = 1;
+ var scy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ if (flipH)
+ {
+ scx = -1;
+ dx = -w - 2 * x;
+ }
+
+ if (flipV)
+ {
+ scy = -1;
+ dy = -h - 2 * y;
+ }
+
+ currentPath.setAttribute('transform', svgTransform + 'scale(' + scx + ' ' + scy + ')' +
+ ' translate('+dx+' '+dy+') ');
+ }
+ else
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+ }
+
+ parentNode.appendChild(currentPath);
+ }
+ }
+ else if (name == 'include-shape')
+ {
+ var stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));
+
+ if (stencil != null)
+ {
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+ var w = Number(node.getAttribute('w')) * sx;
+ var h = Number(node.getAttribute('h')) * sy;
+
+ stencil.renderDom(shape, new mxRectangle(x, y, w, h), parentNode, currentState);
+ }
+ }
+ // Additional labels are currently disabled. Needs fixing of VML
+ // text positon, SVG text rotation and ignored baseline in FF
+ else if (name == 'text')
+ {
+ var str = this.evaluateAttribute(node, 'str', shape.state);
+
+ if (str != null)
+ {
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var align = node.getAttribute('align') || 'left';
+ var valign = node.getAttribute('valign') || 'top';
+
+ if (vml)
+ {
+ // Renders a single line of text with full rotation support
+ currentPath = document.createElement('v:shape');
+ currentPath.style.position = 'absolute';
+ currentPath.style.width = '1px';
+ currentPath.style.height = '1px';
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+
+ var fill = document.createElement('v:fill');
+ fill.color = currentState.fontColor;
+ fill.on = 'true';
+ currentPath.appendChild(fill);
+
+ var stroke = document.createElement('v:stroke');
+ stroke.on = 'false';
+ currentPath.appendChild(stroke);
+
+ var path = document.createElement('v:path');
+ path.textpathok = 'true';
+ path.v = 'm ' + x + ' ' + y + ' l ' + (x + 1) + ' ' + y;
+
+ currentPath.appendChild(path);
+
+ var tp = document.createElement('v:textpath');
+ tp.style.cssText = 'v-text-align:' + align;
+ tp.style.fontSize = Math.round(currentState.fontSize / vmlScale) + 'px';
+
+ // FIXME: Font-family seems to be ignored for textpath
+ tp.style.fontFamily = currentState.fontFamily;
+ tp.string = str;
+ tp.on = 'true';
+
+ // Bold
+ if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ tp.style.fontWeight = 'bold';
+ }
+
+ // Italic
+ if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ tp.style.fontStyle = 'italic';
+ }
+
+ // FIXME: Text decoration not supported in textpath
+ if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ tp.style.textDecoration = 'underline';
+ }
+
+ // LATER: Find vertical center for div via CSS if possible
+ if (valign == 'top')
+ {
+ currentPath.style.top = (y + currentState.fontSize / 2) + 'px';
+ }
+ else if (valign == 'bottom')
+ {
+ currentPath.style.top = (y - currentState.fontSize / 3) + 'px';
+ }
+
+ currentPath.appendChild(tp);
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'text');
+ currentPath.setAttribute('fill', currentState.fontColor);
+ currentPath.setAttribute('font-family', currentState.fontFamily);
+ currentPath.setAttribute('font-size', currentState.fontSize);
+ currentPath.setAttribute('stroke', 'none');
+ currentPath.setAttribute('x', x);
+ currentPath.appendChild(document.createTextNode(str));
+
+ // Bold
+ if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ currentPath.setAttribute('font-weight', 'bold');
+ }
+
+ // Italic
+ if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ currentPath.setAttribute('font-style', 'italic');
+ }
+
+ // Underline
+ if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ currentPath.setAttribute('text-decoration', uline);
+ }
+
+ // Horizontal alignment
+ if (align == 'left')
+ {
+ align = 'start';
+ }
+ else if (align == 'center')
+ {
+ align = 'middle';
+ }
+ else if (align == 'right')
+ {
+ align = 'end';
+ }
+
+ currentPath.setAttribute('text-anchor', align);
+
+ // Vertical alignment
+ // Uses dy because FF ignores alignment-baseline
+ if (valign == 'top')
+ {
+ currentPath.setAttribute('y', y + currentState.fontSize / 5);
+ currentPath.setAttribute('dy', '1ex');
+ }
+ else if (valign == 'middle')
+ {
+ currentPath.setAttribute('y', y + currentState.fontSize / 8);
+ currentPath.setAttribute('dy', '0.5ex');
+ }
+ else
+ {
+ currentPath.setAttribute('y', y);
+ }
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+ }
+
+ parentNode.appendChild(currentPath);
+ }
+ }
+ else if (fillOp || strokeOp || fillStrokeOp)
+ {
+ if (currentPath != null)
+ {
+ var pattern = null;
+
+ if (currentState.dashed)
+ {
+ var f = (vml) ? minScale : Number(currentPath.getAttribute('stroke-width'));
+ var pat = [];
+
+ for (var i = 0; i < currentState.dashpattern.length; i++)
+ {
+ pat.push(Math.max(1, Math.round(Number(currentState.dashpattern[i]) * f)));
+ }
+
+ pattern = pat.join(' ');
+ }
+
+ if (strokeOp || fillStrokeOp)
+ {
+ if (vml)
+ {
+ var stroke = document.createElement('v:stroke');
+ stroke.endcap = currentState.linecap || 'flat';
+ stroke.joinstyle = currentState.linejoin || 'miter';
+ stroke.miterlimit = currentState.miterlimit || '10';
+ currentPath.appendChild(stroke);
+
+ // TODO: Dashpattern support in VML is limited, we should
+ // map this to VML or allow for a separate VML dashstyle.
+ if (pattern != null)
+ {
+ stroke.dashstyle = pattern;
+ }
+ }
+ else
+ {
+ if (currentState.linejoin != null)
+ {
+ currentPath.setAttribute('stroke-linejoin', currentState.linejoin);
+ }
+
+ if (currentState.linecap != null)
+ {
+ // flat is called butt in SVG
+ var value = currentState.linecap;
+
+ if (value == 'flat')
+ {
+ value = 'butt';
+ }
+
+ currentPath.setAttribute('stroke-linecap', value);
+ }
+
+ if (currentState.miterlimit != null)
+ {
+ currentPath.setAttribute('stroke-miterlimit', currentState.miterlimit);
+ }
+
+ // Handles dash pattern
+ if (pattern != null)
+ {
+ currentPath.setAttribute('stroke-dasharray', pattern);
+ }
+ }
+ }
+
+ // Adds the shadow
+ if (background && shape.isShadow)
+ {
+ var dx = mxConstants.SHADOW_OFFSET_X * shape.scale;
+ var dy = mxConstants.SHADOW_OFFSET_Y * shape.scale;
+
+ // Adds the shadow
+ if (vml)
+ {
+ var shadow = document.createElement('v:shadow');
+ shadow.setAttribute('on', 'true');
+ shadow.setAttribute('color', mxConstants.SHADOWCOLOR);
+ shadow.setAttribute('offset', Math.round(dx) + 'px,' + Math.round(dy) + 'px');
+ shadow.setAttribute('opacity', (mxConstants.SHADOW_OPACITY * 100) + '%');
+
+ var stroke = document.createElement('v:stroke');
+ stroke.endcap = currentState.linecap || 'flat';
+ stroke.joinstyle = currentState.linejoin || 'miter';
+ stroke.miterlimit = currentState.miterlimit || '10';
+
+ if (pattern != null)
+ {
+ stroke.dashstyle = pattern;
+ }
+
+ shadow.appendChild(stroke);
+ currentPath.appendChild(shadow);
+ }
+ else
+ {
+ var shadow = currentPath.cloneNode(true);
+ shadow.setAttribute('stroke', mxConstants.SHADOWCOLOR);
+
+ if (currentState.fill != null && (fillOp || fillStrokeOp))
+ {
+ shadow.setAttribute('fill', mxConstants.SHADOWCOLOR);
+ }
+ else
+ {
+ shadow.setAttribute('fill', 'none');
+ }
+
+ shadow.setAttribute('transform', 'translate(' + dx + ' ' + dy + ') ' +
+ (shadow.getAttribute('transform') || ''));
+ shadow.setAttribute('opacity', mxConstants.SHADOW_OPACITY);
+ parentNode.appendChild(shadow);
+ }
+ }
+
+ if (fillOp)
+ {
+ if (vml)
+ {
+ currentPath.stroked = 'false';
+ }
+ else
+ {
+ currentPath.setAttribute('stroke', 'none');
+ }
+ }
+ else if (strokeOp)
+ {
+ if (vml)
+ {
+ currentPath.filled = 'false';
+ }
+ else
+ {
+ currentPath.setAttribute('fill', 'none');
+ }
+ }
+
+ parentNode.appendChild(currentPath);
+ }
+
+ // Background was painted
+ if (background)
+ {
+ background = false;
+ }
+ }
+ else if (name == 'linecap')
+ {
+ currentState.linecap = node.getAttribute('cap');
+ }
+ else if (name == 'linejoin')
+ {
+ currentState.linejoin = node.getAttribute('join');
+ }
+ else if (name == 'miterlimit')
+ {
+ currentState.miterlimit = node.getAttribute('limit');
+ }
+ else if (name == 'dashed')
+ {
+ currentState.dashed = node.getAttribute('dashed') == '1';
+ }
+ else if (name == 'dashpattern')
+ {
+ var value = node.getAttribute('pattern');
+
+ if (value != null && value.length > 0)
+ {
+ currentState.dashpattern = value.split(' ');
+ }
+ }
+ else if (name == 'strokewidth')
+ {
+ currentState.strokeWidth = node.getAttribute('width') * minScale;
+
+ if (vml)
+ {
+ currentState.strokeWidth /= vmlScale;
+ }
+ }
+ else if (name == 'strokecolor')
+ {
+ currentState.stroke = node.getAttribute('color');
+ }
+ else if (name == 'fillcolor')
+ {
+ currentState.fill = node.getAttribute('color');
+ currentState.fillColorAssigned = true;
+ }
+ else if (name == 'alpha')
+ {
+ currentState.alpha = Number(node.getAttribute('alpha'));
+ }
+ else if (name == 'fontcolor')
+ {
+ currentState.fontColor = node.getAttribute('color');
+ }
+ else if (name == 'fontsize')
+ {
+ currentState.fontSize = Number(node.getAttribute('size')) * minScale;
+ }
+ else if (name == 'fontfamily')
+ {
+ currentState.fontFamily = node.getAttribute('family');
+ }
+ else if (name == 'fontstyle')
+ {
+ currentState.fontStyle = Number(node.getAttribute('style'));
+ }
+ };
+
+ // Adds a transparent rectangle in the background for hit-detection in SVG
+ if (!vml)
+ {
+ var rect = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ rect.setAttribute('x', bounds.x);
+ rect.setAttribute('y', bounds.y);
+ rect.setAttribute('width', bounds.width);
+ rect.setAttribute('height', bounds.height);
+ rect.setAttribute('fill', 'none');
+ rect.setAttribute('stroke', 'none');
+ parentNode.appendChild(rect);
+ }
+
+ // Background switches to false after fill/stroke of the background
+ if (this.bgNode != null)
+ {
+ var tmp = this.bgNode.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ renderNode.call(this, tmp);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+ }
+ else
+ {
+ background = false;
+ }
+
+ if (this.fgNode != null)
+ {
+ var tmp = this.fgNode.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ renderNode.call(this, tmp);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+ }
+ }
+};
+
+/**
+ * Function: drawShape
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawShape = function(canvas, state, bounds, background)
+{
+ // TODO: Unify with renderDom, check performance of pluggable shape,
+ // internal structure (array of special structs?), relative and absolute
+ // coordinates (eg. note shape, process vs star, actor etc.), text rendering
+ // and non-proportional scaling, how to implement pluggable edge shapes
+ // (start, segment, end blocks), pluggable markers, how to implement
+ // swimlanes (title area) with this API, add icon, horizontal/vertical
+ // label, indicator for all shapes, rotation
+ var node = (background) ? this.bgNode : this.fgNode;
+
+ if (node != null)
+ {
+ var direction = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION, null);
+ var aspect = this.computeAspect(state, bounds, direction);
+ var minScale = Math.min(aspect.width, aspect.height);
+ var sw = (this.strokewidth == 'inherit') ?
+ Number(mxUtils.getNumber(state.style, mxConstants.STYLE_STROKEWIDTH, 1)) * state.view.scale :
+ Number(this.strokewidth) * minScale;
+ this.lastMoveX = 0;
+ this.lastMoveY = 0;
+ canvas.setStrokeWidth(sw);
+
+ var tmp = node.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ this.drawNode(canvas, state, tmp, aspect);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: computeAspect
+ *
+ * Returns a rectangle that contains the offset in x and y and the horizontal
+ * and vertical scale in width and height used to draw this shape inside the
+ * given <mxRectangle>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be drawn.
+ * bounds - <mxRectangle> that should contain the stencil.
+ * direction - Optional direction of the shape to be darwn.
+ */
+mxStencil.prototype.computeAspect = function(state, bounds, direction)
+{
+ var x0 = bounds.x;
+ var y0 = bounds.y;
+ var sx = bounds.width / this.w0;
+ var sy = bounds.height / this.h0;
+
+ var inverse = (direction == 'north' || direction == 'south');
+
+ if (inverse)
+ {
+ sy = bounds.width / this.h0;
+ sx = bounds.height / this.w0;
+
+ var delta = (bounds.width - bounds.height) / 2;
+
+ x0 += delta;
+ y0 -= delta;
+ }
+
+ if (this.aspect == 'fixed')
+ {
+ sy = Math.min(sx, sy);
+ sx = sy;
+
+ // Centers the shape inside the available space
+ if (inverse)
+ {
+ x0 += (bounds.height - this.w0 * sx) / 2;
+ y0 += (bounds.width - this.h0 * sy) / 2;
+ }
+ else
+ {
+ x0 += (bounds.width - this.w0 * sx) / 2;
+ y0 += (bounds.height - this.h0 * sy) / 2;
+ }
+ }
+
+ return new mxRectangle(x0, y0, sx, sy);
+};
+
+/**
+ * Function: drawNode
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawNode = function(canvas, state, node, aspect)
+{
+ var name = node.nodeName;
+ var x0 = aspect.x;
+ var y0 = aspect.y;
+ var sx = aspect.width;
+ var sy = aspect.height;
+ var minScale = Math.min(sx, sy);
+
+ // LATER: Move to lookup table
+ if (name == 'save')
+ {
+ canvas.save();
+ }
+ else if (name == 'restore')
+ {
+ canvas.restore();
+ }
+ else if (name == 'path')
+ {
+ canvas.begin();
+
+ // Renders the elements inside the given path
+ var childNode = node.firstChild;
+
+ while (childNode != null)
+ {
+ if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ this.drawNode(canvas, state, childNode, aspect);
+ }
+
+ childNode = childNode.nextSibling;
+ }
+ }
+ else if (name == 'close')
+ {
+ canvas.close();
+ }
+ else if (name == 'move')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y')) * sy;
+ canvas.moveTo(this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'line')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y')) * sy;
+ canvas.lineTo(this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'quad')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x2')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y2')) * sy;
+ canvas.quadTo(x0 + Number(node.getAttribute('x1')) * sx,
+ y0 + Number(node.getAttribute('y1')) * sy,
+ this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'curve')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x3')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y3')) * sy;
+ canvas.curveTo(x0 + Number(node.getAttribute('x1')) * sx,
+ y0 + Number(node.getAttribute('y1')) * sy,
+ x0 + Number(node.getAttribute('x2')) * sx,
+ y0 + Number(node.getAttribute('y2')) * sy,
+ this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'arc')
+ {
+ // Arc from stencil is turned into curves in image output
+ var r1 = Number(node.getAttribute('rx')) * sx;
+ var r2 = Number(node.getAttribute('ry')) * sy;
+ var angle = Number(node.getAttribute('x-axis-rotation'));
+ var largeArcFlag = Number(node.getAttribute('large-arc-flag'));
+ var sweepFlag = Number(node.getAttribute('sweep-flag'));
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+
+ var curves = mxUtils.arcToCurves(this.lastMoveX, this.lastMoveY, r1, r2, angle, largeArcFlag, sweepFlag, x, y);
+
+ for (var i = 0; i < curves.length; i += 6)
+ {
+ canvas.curveTo(curves[i], curves[i + 1], curves[i + 2],
+ curves[i + 3], curves[i + 4], curves[i + 5]);
+
+ this.lastMoveX = curves[i + 4];
+ this.lastMoveY = curves[i + 5];
+ }
+ }
+ else if (name == 'rect')
+ {
+ canvas.rect(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ Number(node.getAttribute('w')) * sx,
+ Number(node.getAttribute('h')) * sy);
+ }
+ else if (name == 'roundrect')
+ {
+ var arcsize = node.getAttribute('arcsize');
+
+ if (arcsize == 0)
+ {
+ arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+ }
+
+ var w = Number(node.getAttribute('w')) * sx;
+ var h = Number(node.getAttribute('h')) * sy;
+ var factor = Number(arcsize) / 100;
+ var r = Math.min(w * factor, h * factor);
+
+ canvas.roundrect(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ w, h, r, r);
+ }
+ else if (name == 'ellipse')
+ {
+ canvas.ellipse(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ Number(node.getAttribute('w')) * sx,
+ Number(node.getAttribute('h')) * sy);
+ }
+ else if (name == 'image')
+ {
+ var src = this.evaluateAttribute(node, 'src', state);
+
+ canvas.image(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ Number(node.getAttribute('w')) * sx,
+ Number(node.getAttribute('h')) * sy,
+ src, false, node.getAttribute('flipH') == '1',
+ node.getAttribute('flipV') == '1');
+ }
+ else if (name == 'text')
+ {
+ var str = this.evaluateAttribute(node, 'str', state);
+
+ canvas.text(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ 0, 0, str, node.getAttribute('align'),
+ node.getAttribute('valign'),
+ node.getAttribute('vertical'));
+ }
+ else if (name == 'include-shape')
+ {
+ var stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));
+
+ if (stencil != null)
+ {
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+ var w = Number(node.getAttribute('w')) * sx;
+ var h = Number(node.getAttribute('h')) * sy;
+
+ var tmp = new mxRectangle(x, y, w, h);
+ stencil.drawShape(canvas, state, tmp, true);
+ stencil.drawShape(canvas, state, tmp, false);
+ }
+ }
+ else if (name == 'fillstroke')
+ {
+ canvas.fillAndStroke();
+ }
+ else if (name == 'fill')
+ {
+ canvas.fill();
+ }
+ else if (name == 'stroke')
+ {
+ canvas.stroke();
+ }
+ else if (name == 'strokewidth')
+ {
+ canvas.setStrokeWidth(Number(node.getAttribute('width')) * minScale);
+ }
+ else if (name == 'dashed')
+ {
+ canvas.setDashed(node.getAttribute('dashed') == '1');
+ }
+ else if (name == 'dashpattern')
+ {
+ var value = node.getAttribute('pattern');
+
+ if (value != null)
+ {
+ var tmp = value.split(' ');
+ var pat = [];
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ if (tmp[i].length > 0)
+ {
+ pat.push(Number(tmp[i]) * minScale);
+ }
+ }
+
+ value = pat.join(' ');
+ canvas.setDashPattern(value);
+ }
+ }
+ else if (name == 'strokecolor')
+ {
+ canvas.setStrokeColor(node.getAttribute('color'));
+ }
+ else if (name == 'linecap')
+ {
+ canvas.setLineCap(node.getAttribute('cap'));
+ }
+ else if (name == 'linejoin')
+ {
+ canvas.setLineJoin(node.getAttribute('join'));
+ }
+ else if (name == 'miterlimit')
+ {
+ canvas.setMiterLimit(Number(node.getAttribute('limit')));
+ }
+ else if (name == 'fillcolor')
+ {
+ canvas.setFillColor(node.getAttribute('color'));
+ }
+ else if (name == 'fontcolor')
+ {
+ canvas.setFontColor(node.getAttribute('color'));
+ }
+ else if (name == 'fontstyle')
+ {
+ canvas.setFontStyle(node.getAttribute('style'));
+ }
+ else if (name == 'fontfamily')
+ {
+ canvas.setFontFamily(node.getAttribute('family'));
+ }
+ else if (name == 'fontsize')
+ {
+ canvas.setFontSize(Number(node.getAttribute('size')) * minScale);
+ }
+};
diff --git a/src/js/shape/mxStencilRegistry.js b/src/js/shape/mxStencilRegistry.js
new file mode 100644
index 0000000..7621573
--- /dev/null
+++ b/src/js/shape/mxStencilRegistry.js
@@ -0,0 +1,53 @@
+/**
+ * $Id: mxStencilRegistry.js,v 1.2 2011-07-15 12:57:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ *
+ * Code to add stencils.
+ *
+ * (code)
+ * var req = mxUtils.load('test/stencils.xml');
+ * var root = req.getDocumentElement();
+ * var shape = root.firstChild;
+ *
+ * while (shape != null)
+ * {
+ * if (shape.nodeType == mxConstants.NODETYPE_ELEMENT)
+ * {
+ * mxStencilRegistry.addStencil(shape.getAttribute('name'), new mxStencil(shape));
+ * }
+ *
+ * shape = shape.nextSibling;
+ * }
+ * (end)
+ */
+var mxStencilRegistry =
+{
+ /**
+ * Class: mxStencilRegistry
+ *
+ * A singleton class that provides a registry for stencils and the methods
+ * for painting those stencils onto a canvas or into a DOM.
+ */
+ stencils: [],
+
+ /**
+ * Function: addStencil
+ *
+ * Adds the given <mxStencil>.
+ */
+ addStencil: function(name, stencil)
+ {
+ mxStencilRegistry.stencils[name] = stencil;
+ },
+
+ /**
+ * Function: getStencil
+ *
+ * Returns the <mxStencil> for the given name.
+ */
+ getStencil: function(name)
+ {
+ return mxStencilRegistry.stencils[name];
+ }
+
+};
diff --git a/src/js/shape/mxStencilShape.js b/src/js/shape/mxStencilShape.js
new file mode 100644
index 0000000..9c95f8b
--- /dev/null
+++ b/src/js/shape/mxStencilShape.js
@@ -0,0 +1,209 @@
+/**
+ * $Id: mxStencilShape.js,v 1.10 2012-07-16 10:22:44 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxStencilShape
+ *
+ * Implements a shape based on a <mxStencil>.
+ *
+ * Constructor: mxStencilShape
+ *
+ * Constructs a new generic shape.
+ */
+function mxStencilShape(stencil)
+{
+ this.stencil = stencil;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxStencilShape.prototype = new mxShape();
+mxStencilShape.prototype.constructor = mxStencilShape;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Always prefers VML in mixed mode for stencil shapes. Default is false.
+ */
+mxStencilShape.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Always prefers VML in prefer HTML mode for stencil shapes. Default is false.
+ */
+mxStencilShape.prototype.preferModeHtml = false;
+
+/**
+ * Variable: stencil
+ *
+ * Holds the <mxStencil> that defines the shape.
+ */
+mxStencilShape.prototype.stencil = null;
+
+/**
+ * Variable: state
+ *
+ * Holds the <mxCellState> associated with this shape.
+ */
+mxStencilShape.prototype.state = null;
+
+/**
+ * Variable: vmlScale
+ *
+ * Renders VML with a scale of 4.
+ */
+mxStencilShape.prototype.vmlScale = 4;
+
+/**
+ * Function: apply
+ *
+ * Extends <mxShape> apply to keep a reference to the <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxStencilShape.prototype.apply = function(state)
+{
+ this.state = state;
+ mxShape.prototype.apply.apply(this, arguments);
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxStencilShape.prototype.createSvg = function()
+{
+ var node = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.configureSvgShape(node);
+
+ return node;
+};
+
+/**
+ * Function: configureHtmlShape
+ *
+ * Overrides method to set the overflow style to visible.
+ */
+mxStencilShape.prototype.configureHtmlShape = function(node)
+{
+ mxShape.prototype.configureHtmlShape.apply(this, arguments);
+
+ if (!mxUtils.isVml(node))
+ {
+ node.style.overflow = 'visible';
+ }
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxStencilShape.prototype.createVml = function()
+{
+ var name = (document.documentMode == 8) ? 'div' : 'v:group';
+ var node = document.createElement(name);
+ this.configureTransparentBackground(node);
+ node.style.position = 'absolute';
+
+ return node;
+};
+
+/**
+ * Function: configureVmlShape
+ *
+ * Configures the specified VML node by applying the current color,
+ * bounds, shadow, opacity etc.
+ */
+mxStencilShape.prototype.configureVmlShape = function(node)
+{
+ // do nothing
+};
+
+/**
+ * Function: redraw
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxStencilShape.prototype.redraw = function()
+{
+ this.updateBoundingBox();
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.redrawShape();
+ }
+ else
+ {
+ this.node.style.visibility = 'hidden';
+ this.redrawShape();
+ this.node.style.visibility = 'visible';
+ }
+};
+
+/**
+ * Function: redrawShape
+ *
+ * Updates the SVG or VML shape.
+ */
+mxStencilShape.prototype.redrawShape = function()
+{
+ // LATER: Update existing DOM nodes to improve repaint performance
+ if (this.dialect != mxConstants.DIALECT_SVG)
+ {
+ this.node.style.left = Math.round(this.bounds.x) + 'px';
+ this.node.style.top = Math.round(this.bounds.y) + 'px';
+ var w = Math.round(this.bounds.width);
+ var h = Math.round(this.bounds.height);
+ this.node.style.width = w + 'px';
+ this.node.style.height = h + 'px';
+
+ var node = this.node;
+
+ // Workaround for VML rendering bug in IE8 standards mode where all VML must be
+ // parsed via assigning the innerHTML of the parent HTML node to keep all event
+ // handlers referencing node and support rotation via v:group parent element.
+ if (this.node.nodeName == 'DIV')
+ {
+ node = document.createElement('v:group');
+ node.style.position = 'absolute';
+ node.style.left = '0px';
+ node.style.top = '0px';
+ node.style.width = w + 'px';
+ node.style.height = h + 'px';
+ }
+ else
+ {
+ node.innerHTML = '';
+ }
+
+ if (mxUtils.isVml(node))
+ {
+ var s = (document.documentMode != 8) ? this.vmlScale : 1;
+ node.coordsize = (w * s) + ',' + (h * s);
+ }
+
+ this.stencil.renderDom(this, this.bounds, node);
+
+ if(this.node != node)
+ {
+ // Forces parsing in IE8 standards mode
+ this.node.innerHTML = node.outerHTML;
+ }
+ }
+ else
+ {
+ while (this.node.firstChild != null)
+ {
+ this.node.removeChild(this.node.firstChild);
+ }
+
+ this.stencil.renderDom(this, this.bounds, this.node);
+ }
+};
diff --git a/src/js/shape/mxSwimlane.js b/src/js/shape/mxSwimlane.js
new file mode 100644
index 0000000..22720fd
--- /dev/null
+++ b/src/js/shape/mxSwimlane.js
@@ -0,0 +1,553 @@
+/**
+ * $Id: mxSwimlane.js,v 1.43 2011-11-04 13:54:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSwimlane
+ *
+ * Extends <mxShape> to implement a swimlane shape.
+ * This shape is registered under <mxConstants.SHAPE_SWIMLANE>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxSwimlane
+ *
+ * Constructs a new swimlane shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxSwimlane(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxSwimlane.prototype = new mxShape();
+mxSwimlane.prototype.constructor = mxSwimlane;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxSwimlane.prototype.vmlNodes = mxSwimlane.prototype.vmlNodes.concat(['label', 'content', 'imageNode', 'separator']);
+
+/**
+ * Variable: imageSize
+ *
+ * Default imagewidth and imageheight if an image but no imagewidth
+ * and imageheight are defined in the style. Value is 16.
+ */
+mxSwimlane.prototype.imageSize = 16;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode. This is for better
+ * handling of event-transparency of the content area.
+ */
+mxSwimlane.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode. This is for better
+ * handling of event-transparency of the content area.
+ */
+mxRhombus.prototype.preferModeHtml = false;
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML node to represent this shape.
+ */
+mxSwimlane.prototype.createHtml = function()
+{
+ var node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+ node.style.background = '';
+ node.style.backgroundColor = '';
+ node.style.borderStyle = 'none';
+
+ // Adds a node that will contain the text label
+ this.label = document.createElement('DIV');
+ this.configureHtmlShape(this.label);
+ node.appendChild(this.label);
+
+ // Adds a node for the content area of the swimlane
+ this.content = document.createElement('DIV');
+ this.configureHtmlShape(this.content);
+ this.content.style.backgroundColor = '';
+
+ // Sets border styles depending on orientation
+ if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ this.content.style.borderTopStyle = 'none';
+ }
+ else
+ {
+ this.content.style.borderLeftStyle = 'none';
+ }
+
+ this.content.style.cursor = 'default';
+ node.appendChild(this.content);
+
+ // Adds a node for the separator
+ var color = this.style[mxConstants.STYLE_SEPARATORCOLOR];
+
+ if (color != null)
+ {
+ this.separator = document.createElement('DIV');
+ this.separator.style.borderColor = color;
+ this.separator.style.borderLeftStyle = 'dashed';
+ node.appendChild(this.separator);
+ }
+
+ // Adds a node for the image
+ if (this.image != null)
+ {
+ this.imageNode = mxUtils.createImage(this.image);
+ this.configureHtmlShape(this.imageNode);
+ this.imageNode.style.borderStyle = 'none';
+ node.appendChild(this.imageNode);
+ }
+
+ return node;
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Overrides to avoid filled content area in HTML and updates the shadow
+ * in SVG.
+ */
+mxSwimlane.prototype.reconfigure = function(node)
+{
+ mxShape.prototype.reconfigure.apply(this, arguments);
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ if (this.shadowNode != null)
+ {
+ this.updateSvgShape(this.shadowNode);
+
+ if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ this.shadowNode.setAttribute('height', this.startSize*this.scale);
+ }
+ else
+ {
+ this.shadowNode.setAttribute('width', this.startSize*this.scale);
+ }
+ }
+ }
+ else if (!mxUtils.isVml(this.node))
+ {
+ this.node.style.background = '';
+ this.node.style.backgroundColor = '';
+ }
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxSwimlane.prototype.redrawHtml = function()
+{
+ this.updateHtmlShape(this.node);
+ this.node.style.background = '';
+ this.node.style.backgroundColor = '';
+ this.startSize = parseInt(mxUtils.getValue(this.style,
+ mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+ this.updateHtmlShape(this.label);
+ this.label.style.top = '0px';
+ this.label.style.left = '0px';
+
+ if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ this.startSize = Math.min(this.startSize, this.bounds.height);
+ this.label.style.height = (this.startSize * this.scale)+'px'; // relative
+ this.updateHtmlShape(this.content);
+ this.content.style.background = '';
+ this.content.style.backgroundColor = '';
+
+ var h = this.startSize*this.scale;
+
+ this.content.style.top = h+'px';
+ this.content.style.left = '0px';
+ this.content.style.height = Math.max(1, this.bounds.height - h)+'px';
+
+ if (this.separator != null)
+ {
+ this.separator.style.left = Math.round(this.bounds.width)+'px';
+ this.separator.style.top = Math.round(this.startSize*this.scale)+'px';
+ this.separator.style.width = '1px';
+ this.separator.style.height = Math.round(this.bounds.height)+'px';
+ this.separator.style.borderWidth = Math.round(this.scale)+'px';
+ }
+
+ if (this.imageNode != null)
+ {
+ this.imageNode.style.left = (this.bounds.width-this.imageSize-4)+'px';
+ this.imageNode.style.top = '0px';
+ // TODO: Use imageWidth and height from style if available
+ this.imageNode.style.width = Math.round(this.imageSize*this.scale)+'px';
+ this.imageNode.style.height = Math.round(this.imageSize*this.scale)+'px';
+ }
+ }
+ else
+ {
+ this.startSize = Math.min(this.startSize, this.bounds.width);
+ this.label.style.width = (this.startSize * this.scale)+'px'; // relative
+ this.updateHtmlShape(this.content);
+ this.content.style.background = '';
+ this.content.style.backgroundColor = '';
+
+ var w = this.startSize*this.scale;
+
+ this.content.style.top = '0px';
+ this.content.style.left = w+'px';
+ this.content.style.width = Math.max(0, this.bounds.width - w)+'px';
+
+ if (this.separator != null)
+ {
+ this.separator.style.left = Math.round(this.startSize*this.scale)+'px';
+ this.separator.style.top = Math.round(this.bounds.height)+'px';
+ this.separator.style.width = Math.round(this.bounds.width)+'px';
+ this.separator.style.height = '1px';
+ }
+
+ if (this.imageNode != null)
+ {
+ this.imageNode.style.left = (this.bounds.width-this.imageSize-4)+'px';
+ this.imageNode.style.top = '0px';
+ this.imageNode.style.width = this.imageSize*this.scale+'px';
+ this.imageNode.style.height = this.imageSize*this.scale+'px';
+ }
+ }
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node(s) to represent this shape.
+ */
+mxSwimlane.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+ var name = (this.isRounded) ? 'v:roundrect' : 'v:rect';
+ this.label = document.createElement(name);
+
+ // First configure the label with all settings
+ this.configureVmlShape(this.label);
+
+ if (this.isRounded)
+ {
+ this.label.setAttribute('arcsize', '20%');
+ }
+
+ // Disables stuff and configures the rest
+ this.isShadow = false;
+ this.configureVmlShape(node);
+ node.coordorigin = '0,0';
+ node.appendChild(this.label);
+
+ this.content = document.createElement(name);
+
+ var tmp = this.fill;
+ this.fill = null;
+
+ this.configureVmlShape(this.content);
+ node.style.background = '';
+
+ if (this.isRounded)
+ {
+ this.content.setAttribute('arcsize', '4%');
+ }
+
+ this.fill = tmp;
+ this.content.style.borderBottom = '0px';
+
+ node.appendChild(this.content);
+
+ var color = this.style[mxConstants.STYLE_SEPARATORCOLOR];
+
+ if (color != null)
+ {
+ this.separator = document.createElement('v:shape');
+ this.separator.style.position = 'absolute';
+ this.separator.strokecolor = color;
+
+ var strokeNode = document.createElement('v:stroke');
+ strokeNode.dashstyle = '2 2';
+ this.separator.appendChild(strokeNode);
+
+ node.appendChild(this.separator);
+ }
+
+ if (this.image != null)
+ {
+ this.imageNode = document.createElement('v:image');
+ this.imageNode.src = this.image;
+ this.configureVmlShape(this.imageNode);
+ this.imageNode.stroked = 'false';
+
+ node.appendChild(this.imageNode);
+ }
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxSwimlane.prototype.redrawVml = function()
+{
+ var x = Math.round(this.bounds.x);
+ var y = Math.round(this.bounds.y);
+ var w = Math.round(this.bounds.width);
+ var h = Math.round(this.bounds.height);
+
+ this.updateVmlShape(this.node);
+ this.node.coordsize = w + ',' + h;
+
+ this.updateVmlShape(this.label);
+ this.label.style.top = '0px';
+ this.label.style.left = '0px';
+ this.label.style.rotation = null;
+
+ this.startSize = parseInt(mxUtils.getValue(this.style,
+ mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+ var start = Math.round(this.startSize * this.scale);
+
+ if (this.separator != null)
+ {
+ this.separator.coordsize = w + ',' + h;
+ this.separator.style.left = x + 'px';
+ this.separator.style.top = y + 'px';
+ this.separator.style.width = w + 'px';
+ this.separator.style.height = h + 'px';
+ }
+
+ if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ start = Math.min(start, this.bounds.height);
+ this.label.style.height = start + 'px'; // relative
+ this.updateVmlShape(this.content);
+ this.content.style.background = '';
+ this.content.style.top = start + 'px';
+ this.content.style.left = '0px';
+ this.content.style.height = Math.max(0, h - start)+'px';
+
+ if (this.separator != null)
+ {
+ var d = 'm ' + (w - x) + ' ' + (start - y) +
+ ' l ' + (w - x) + ' ' + (h - y) + ' e';
+ this.separator.path = d;
+ }
+
+ if (this.imageNode != null)
+ {
+ var img = Math.round(this.imageSize*this.scale);
+
+ this.imageNode.style.left = (w-img-4)+'px';
+ this.imageNode.style.top = '0px';
+ this.imageNode.style.width = img + 'px';
+ this.imageNode.style.height = img + 'px';
+ }
+ }
+ else
+ {
+ start = Math.min(start, this.bounds.width);
+ this.label.style.width = start + 'px'; // relative
+ this.updateVmlShape(this.content);
+ this.content.style.background = '';
+ this.content.style.top = '0px';
+ this.content.style.left = start + 'px';
+ this.content.style.width = Math.max(0, w - start) + 'px';
+
+ if (this.separator != null)
+ {
+ var d = 'm ' + (start - x) + ' ' + (h - y) +
+ ' l ' + (w - x) + ' ' + (h - y) + ' e';
+ this.separator.path = d;
+ }
+
+ if (this.imageNode != null)
+ {
+ var img = Math.round(this.imageSize * this.scale);
+
+ this.imageNode.style.left = (w - img - 4)+'px';
+ this.imageNode.style.top = '0px';
+ this.imageNode.style.width = img + 'px';
+ this.imageNode.style.height = img + 'px';
+ }
+ }
+
+ this.content.style.rotation = null;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxSwimlane.prototype.createSvg = function()
+{
+ var node = this.createSvgGroup('rect');
+
+ if (this.isRounded)
+ {
+ this.innerNode.setAttribute('rx', 10);
+ this.innerNode.setAttribute('ry', 10);
+ }
+
+ this.content = document.createElementNS(mxConstants.NS_SVG, 'path');
+ this.configureSvgShape(this.content);
+ this.content.setAttribute('fill', 'none');
+
+ if (this.isRounded)
+ {
+ this.content.setAttribute('rx', 10);
+ this.content.setAttribute('ry', 10);
+ }
+
+ node.appendChild(this.content);
+ var color = this.style[mxConstants.STYLE_SEPARATORCOLOR];
+
+ if (color != null)
+ {
+ this.separator = document.createElementNS(mxConstants.NS_SVG, 'line');
+
+ this.separator.setAttribute('stroke', color);
+ this.separator.setAttribute('fill', 'none');
+ this.separator.setAttribute('stroke-dasharray', '2, 2');
+
+ node.appendChild(this.separator);
+ }
+
+ if (this.image != null)
+ {
+ this.imageNode = document.createElementNS(mxConstants.NS_SVG, 'image');
+
+ this.imageNode.setAttributeNS(mxConstants.NS_XLINK, 'href', this.image);
+ this.configureSvgShape(this.imageNode);
+
+ node.appendChild(this.imageNode);
+ }
+
+ return node;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxSwimlane.prototype.redrawSvg = function()
+{
+ var tmp = this.isRounded;
+ this.isRounded = false;
+
+ this.updateSvgShape(this.innerNode);
+ this.updateSvgShape(this.content);
+ var horizontal = mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, true);
+ this.startSize = parseInt(mxUtils.getValue(this.style,
+ mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+ var ss = this.startSize * this.scale;
+
+ // Updates the size of the shadow node
+ if (this.shadowNode != null)
+ {
+ this.updateSvgShape(this.shadowNode);
+
+ if (horizontal)
+ {
+ this.shadowNode.setAttribute('height', ss);
+ }
+ else
+ {
+ this.shadowNode.setAttribute('width', ss);
+ }
+ }
+
+ this.isRounded = tmp;
+
+ this.content.removeAttribute('x');
+ this.content.removeAttribute('y');
+ this.content.removeAttribute('width');
+ this.content.removeAttribute('height');
+
+ var crisp = (this.crisp && mxClient.IS_IE) ? 0.5 : 0;
+ var x = Math.round(this.bounds.x) + crisp;
+ var y = Math.round(this.bounds.y) + crisp;
+ var w = Math.round(this.bounds.width);
+ var h = Math.round(this.bounds.height);
+
+ if (horizontal)
+ {
+ ss = Math.min(ss, h);
+ this.innerNode.setAttribute('height', ss);
+ var points = 'M ' + x + ' ' + (y + ss) +
+ ' l 0 ' + (h - ss) + ' l ' + w + ' 0' +
+ ' l 0 ' + (ss - h);
+ this.content.setAttribute('d', points);
+
+ if (this.separator != null)
+ {
+ this.separator.setAttribute('x1', x + w);
+ this.separator.setAttribute('y1', y + ss);
+ this.separator.setAttribute('x2', x + w);
+ this.separator.setAttribute('y2', y + h);
+ }
+
+ if (this.imageNode != null)
+ {
+ this.imageNode.setAttribute('x', x + w - this.imageSize - 4);
+ this.imageNode.setAttribute('y', y);
+ this.imageNode.setAttribute('width', this.imageSize * this.scale + 'px');
+ this.imageNode.setAttribute('height', this.imageSize * this.scale + 'px');
+ }
+ }
+ else
+ {
+ ss = Math.min(ss, w);
+ this.innerNode.setAttribute('width', ss);
+ var points = 'M ' + (x + ss) + ' ' + y +
+ ' l ' + (w - ss) + ' 0' + ' l 0 ' + h +
+ ' l ' + (ss - w) + ' 0';
+ this.content.setAttribute('d', points);
+
+ if (this.separator != null)
+ {
+ this.separator.setAttribute('x1', x + ss);
+ this.separator.setAttribute('y1', y + h);
+ this.separator.setAttribute('x2', x + w);
+ this.separator.setAttribute('y2', y + h);
+ }
+
+ if (this.imageNode != null)
+ {
+ this.imageNode.setAttribute('x', x + w - this.imageSize - 4);
+ this.imageNode.setAttribute('y', y);
+ this.imageNode.setAttribute('width', this.imageSize * this.scale + 'px');
+ this.imageNode.setAttribute('height', this.imageSize * this.scale + 'px');
+ }
+ }
+};
diff --git a/src/js/shape/mxText.js b/src/js/shape/mxText.js
new file mode 100644
index 0000000..de44b59
--- /dev/null
+++ b/src/js/shape/mxText.js
@@ -0,0 +1,1811 @@
+/**
+ * $Id: mxText.js,v 1.174 2012-09-27 10:20:30 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxText
+ *
+ * Extends <mxShape> to implement a text shape. To change vertical text from
+ * bottom to top to top to bottom, the following code can be used:
+ *
+ * (code)
+ * mxText.prototype.ieVerticalFilter = 'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)';
+ * mxText.prototype.verticalTextDegree = 90;
+ *
+ * mxText.prototype.getVerticalOffset = function(offset)
+ * {
+ * return new mxPoint(-offset.y, offset.x);
+ * };
+ * (end)
+ *
+ * Constructor: mxText
+ *
+ * Constructs a new text shape.
+ *
+ * Parameters:
+ *
+ * value - String that represents the text to be displayed. This is stored in
+ * <value>.
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * align - Specifies the horizontal alignment. Default is ''. This is stored in
+ * <align>.
+ * valign - Specifies the vertical alignment. Default is ''. This is stored in
+ * <valign>.
+ * color - String that specifies the text color. Default is 'black'. This is
+ * stored in <color>.
+ * family - String that specifies the font family. Default is
+ * <mxConstants.DEFAULT_FONTFAMILY>. This is stored in <family>.
+ * size - Integer that specifies the font size. Default is
+ * <mxConstants.DEFAULT_FONTSIZE>. This is stored in <size>.
+ * fontStyle - Specifies the font style. Default is 0. This is stored in
+ * <fontStyle>.
+ * spacing - Integer that specifies the global spacing. Default is 2. This is
+ * stored in <spacing>.
+ * spacingTop - Integer that specifies the top spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingTop>.
+ * spacingRight - Integer that specifies the right spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingRight>.
+ * spacingBottom - Integer that specifies the bottom spacing. Default is 0.The
+ * sum of the spacing and this is stored in <spacingBottom>.
+ * spacingLeft - Integer that specifies the left spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingLeft>.
+ * horizontal - Boolean that specifies if the label is horizontal. Default is
+ * true. This is stored in <horizontal>.
+ * background - String that specifies the background color. Default is null.
+ * This is stored in <background>.
+ * border - String that specifies the label border color. Default is null.
+ * This is stored in <border>.
+ * wrap - Specifies if word-wrapping should be enabled. Default is false.
+ * This is stored in <wrap>.
+ * clipped - Specifies if the label should be clipped. Default is false.
+ * This is stored in <clipped>.
+ * overflow - Value of the overflow style. Default is 'visible'.
+ */
+function mxText(value, bounds, align, valign, color,
+ family, size, fontStyle, spacing, spacingTop, spacingRight,
+ spacingBottom, spacingLeft, horizontal, background, border,
+ wrap, clipped, overflow, labelPadding)
+{
+ this.value = value;
+ this.bounds = bounds;
+ this.color = (color != null) ? color : 'black';
+ this.align = (align != null) ? align : '';
+ this.valign = (valign != null) ? valign : '';
+ this.family = (family != null) ? family : mxConstants.DEFAULT_FONTFAMILY;
+ this.size = (size != null) ? size : mxConstants.DEFAULT_FONTSIZE;
+ this.fontStyle = (fontStyle != null) ? fontStyle : 0;
+ this.spacing = parseInt(spacing || 2);
+ this.spacingTop = this.spacing + parseInt(spacingTop || 0);
+ this.spacingRight = this.spacing + parseInt(spacingRight || 0);
+ this.spacingBottom = this.spacing + parseInt(spacingBottom || 0);
+ this.spacingLeft = this.spacing + parseInt(spacingLeft || 0);
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.background = background;
+ this.border = border;
+ this.wrap = (wrap != null) ? wrap : false;
+ this.clipped = (clipped != null) ? clipped : false;
+ this.overflow = (overflow != null) ? overflow : 'visible';
+ this.labelPadding = (labelPadding != null) ? labelPadding : 0;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxText.prototype = new mxShape();
+mxText.prototype.constructor = mxText;
+
+/**
+ * Variable: replaceLinefeeds
+ *
+ * Specifies if linefeeds in HTML labels should be replaced with BR tags.
+ * Default is true. This is also used in <mxImageExport> to export the label.
+ */
+mxText.prototype.replaceLinefeeds = true;
+
+/**
+ * Variable: ieVerticalFilter
+ *
+ * Holds the filter definition for vertical text in IE. Default is
+ * progid:DXImageTransform.Microsoft.BasicImage(rotation=3).
+ */
+mxText.prototype.ieVerticalFilter = 'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
+
+/**
+ * Variable: verticalTextDegree
+ *
+ * Specifies the degree to be used for vertical text. Default is -90.
+ */
+mxText.prototype.verticalTextDegree = -90;
+
+/**
+ * Variable: forceIgnoreStringSize
+ *
+ * Specifies if the string size should always be ignored. Default is false.
+ * This can be used to improve rendering speed in slow browsers. This can be
+ * used if all labels are smaller than the vertex width. String sizes are
+ * ignored by default for labels which are left aligned with no background and
+ * border or if the overflow is set to fill.
+ */
+mxText.prototype.forceIgnoreStringSize = false;
+
+/**
+ * Function: isStyleSet
+ *
+ * Returns true if the given font style (bold, italic etc)
+ * is true in this shape's fontStyle.
+ *
+ * Parameters:
+ *
+ * style - Fontstyle constant from <mxConstants>.
+ */
+mxText.prototype.isStyleSet = function(style)
+{
+ return (this.fontStyle & style) == style;
+};
+
+/**
+ * Function: create
+ *
+ * Override to create HTML regardless of gradient and
+ * rounded property.
+ */
+mxText.prototype.create = function(container)
+{
+ var node = null;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ node = this.createSvg();
+ }
+ else if (this.dialect == mxConstants.DIALECT_STRICTHTML ||
+ this.dialect == mxConstants.DIALECT_PREFERHTML ||
+ !mxUtils.isVml(container))
+ {
+ if (mxClient.IS_SVG && !mxClient.NO_FO)
+ {
+ node = this.createForeignObject();
+ }
+ else
+ {
+ node = this.createHtml();
+ }
+ }
+ else
+ {
+ node = this.createVml();
+ }
+
+ return node;
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Overrides method to do nothing.
+ */
+mxText.prototype.updateBoundingBox = function()
+{
+ // do nothing
+};
+
+/**
+ * Function: createForeignObject
+ *
+ * Creates and returns the foreignObject node to represent this shape.
+ */
+mxText.prototype.createForeignObject = function()
+{
+ var node = document.createElementNS(mxConstants.NS_SVG, 'g');
+
+ var fo = document.createElementNS(mxConstants.NS_SVG, 'foreignObject');
+ fo.setAttribute('pointer-events', 'fill');
+
+ // Ignored in FF
+ if (this.overflow == 'hidden')
+ {
+ fo.style.overflow = 'hidden';
+ }
+ else
+ {
+ // Fill and default are visible
+ fo.style.overflow = 'visible';
+ }
+
+ var body = document.createElement('div');
+ body.style.margin = '0px';
+ body.style.height = '100%';
+
+ fo.appendChild(body);
+ node.appendChild(fo);
+
+ return node;
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML node to represent this shape.
+ */
+mxText.prototype.createHtml = function()
+{
+ var table = this.createHtmlTable();
+ table.style.position = 'absolute';
+
+ return table;
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node(s) to represent this shape.
+ */
+mxText.prototype.createVml = function()
+{
+ return document.createElement('v:textbox');
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.redrawHtml = function()
+{
+ this.redrawVml();
+};
+
+/**
+ * Function: getOffset
+ *
+ * Returns the description of the space between the <bounds> size and the label
+ * size as an <mxPoint>.
+ */
+mxText.prototype.getOffset = function(outerWidth, outerHeight, actualWidth, actualHeight, horizontal)
+{
+ horizontal = (horizontal != null) ? horizontal : this.horizontal;
+
+ var tmpalign = (horizontal) ? this.align : this.valign;
+ var tmpvalign = (horizontal) ? this.valign : this.align;
+ var dx = actualWidth - outerWidth;
+ var dy = actualHeight - outerHeight;
+
+ if (tmpalign == mxConstants.ALIGN_CENTER || tmpalign == mxConstants.ALIGN_MIDDLE)
+ {
+ dx = Math.round(dx / 2);
+ }
+ else if (tmpalign == mxConstants.ALIGN_LEFT || tmpalign === mxConstants.ALIGN_TOP)
+ {
+ dx = (horizontal) ? 0 : (actualWidth - actualHeight) / 2;
+ }
+ else if (!horizontal) // BOTTOM
+ {
+ dx = (actualWidth + actualHeight) / 2 - outerWidth;
+ }
+
+ if (tmpvalign == mxConstants.ALIGN_MIDDLE || tmpvalign == mxConstants.ALIGN_CENTER)
+ {
+ dy = Math.round(dy / 2);
+ }
+ else if (tmpvalign == mxConstants.ALIGN_TOP || tmpvalign == mxConstants.ALIGN_LEFT)
+ {
+ dy = (horizontal) ? 0 : (actualHeight + actualWidth) / 2 - outerHeight;
+ }
+ else if (!horizontal) // RIGHT
+ {
+ dy = (actualHeight - actualWidth) / 2;
+ }
+
+ return new mxPoint(dx, dy);
+};
+
+/**
+ * Function: getSpacing
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.getSpacing = function(horizontal)
+{
+ horizontal = (horizontal != null) ? horizontal : this.horizontal;
+
+ var dx = 0;
+ var dy = 0;
+
+ if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ dx = (this.spacingLeft - this.spacingRight) / 2;
+ }
+ else if (this.align == mxConstants.ALIGN_RIGHT)
+ {
+ dx = -this.spacingRight;
+ }
+ else
+ {
+ dx = this.spacingLeft;
+ }
+
+ if (this.valign == mxConstants.ALIGN_MIDDLE)
+ {
+ dy = (this.spacingTop - this.spacingBottom) / 2;
+ }
+ else if (this.valign == mxConstants.ALIGN_BOTTOM)
+ {
+ dy = -this.spacingBottom;
+ }
+ else
+ {
+ dy = this.spacingTop;
+ }
+
+ return (horizontal) ? new mxPoint(dx, dy) : new mxPoint(dy, dx);
+};
+
+/**
+ * Function: createHtmlTable
+ *
+ * Creates and returns a HTML table with a table body and a single row with a
+ * single cell.
+ */
+mxText.prototype.createHtmlTable = function()
+{
+ var table = document.createElement('table');
+ table.style.borderCollapse = 'collapse';
+ var tbody = document.createElement('tbody');
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+
+ // Workaround for ignored table height in IE9 standards mode
+ if (document.documentMode >= 9)
+ {
+ // FIXME: Ignored in print preview for IE9 standards mode
+ td.style.height = '100%';
+ }
+
+ tr.appendChild(td);
+ tbody.appendChild(tr);
+ table.appendChild(tbody);
+
+ return table;
+};
+
+/**
+ * Function: updateTableStyle
+ *
+ * Updates the style of the given HTML table and the value
+ * within the table.
+ */
+mxText.prototype.updateHtmlTable = function(table, scale)
+{
+ scale = (scale != null) ? scale : 1;
+ var td = table.firstChild.firstChild.firstChild;
+
+ // Reset of width required to measure actual width after word wrap
+ if (this.wrap)
+ {
+ table.style.width = '';
+ }
+
+ // Updates the value
+ if (mxUtils.isNode(this.value))
+ {
+ if (td.firstChild != this.value)
+ {
+ if (td.firstChild != null)
+ {
+ td.removeChild(td.firstChild);
+ }
+
+ td.appendChild(this.value);
+ }
+ }
+ else
+ {
+ if (this.lastValue != this.value)
+ {
+ td.innerHTML = (this.replaceLinefeeds) ? this.value.replace(/\n/g, '<br/>') : this.value;
+ this.lastValue = this.value;
+ }
+ }
+
+ // Font style
+ var fontSize = Math.round(this.size * scale);
+
+ if (fontSize <= 0)
+ {
+ table.style.visibility = 'hidden';
+ }
+ else
+ {
+ // Do not use visible here as it will clone
+ // all labels while panning in IE
+ table.style.visibility = '';
+ }
+
+ table.style.fontSize = fontSize + 'px';
+ table.style.color = this.color;
+ table.style.fontFamily = this.family;
+
+ // Bold
+ if (this.isStyleSet(mxConstants.FONT_BOLD))
+ {
+ table.style.fontWeight = 'bold';
+ }
+ else
+ {
+ table.style.fontWeight = 'normal';
+ }
+
+ // Italic
+ if (this.isStyleSet(mxConstants.FONT_ITALIC))
+ {
+ table.style.fontStyle = 'italic';
+ }
+ else
+ {
+ table.style.fontStyle = '';
+ }
+
+ // Underline
+ if (this.isStyleSet(mxConstants.FONT_UNDERLINE))
+ {
+ table.style.textDecoration = 'underline';
+ }
+ else
+ {
+ table.style.textDecoration = '';
+ }
+
+ // Font shadow (only available in IE)
+ if (mxClient.IS_IE)
+ {
+ if (this.isStyleSet(mxConstants.FONT_SHADOW))
+ {
+ td.style.filter = 'Shadow(Color=#666666,'+'Direction=135,Strength=%)';
+ }
+ else
+ {
+ td.style.removeAttribute('filter');
+ }
+ }
+
+ // Horizontal and vertical alignment
+ td.style.textAlign =
+ (this.align == mxConstants.ALIGN_RIGHT) ? 'right' :
+ ((this.align == mxConstants.ALIGN_CENTER) ? 'center' :
+ 'left');
+ td.style.verticalAlign =
+ (this.valign == mxConstants.ALIGN_BOTTOM) ? 'bottom' :
+ ((this.valign == mxConstants.ALIGN_MIDDLE) ? 'middle' :
+ 'top');
+
+ // Background style (Must use TD not TABLE for Firefox when rotated)
+ if (this.value.length > 0 && this.background != null)
+ {
+ td.style.background = this.background;
+ }
+ else
+ {
+ td.style.background = '';
+ }
+
+ td.style.padding = this.labelPadding + 'px';
+
+ if (this.value.length > 0 && this.border != null)
+ {
+ table.style.borderColor = this.border;
+ table.style.borderWidth = '1px';
+ table.style.borderStyle = 'solid';
+ }
+ else
+ {
+ table.style.borderStyle = 'none';
+ }
+};
+
+/**
+ * Function: getTableSize
+ *
+ * Returns the actual size of the table.
+ */
+mxText.prototype.getTableSize = function(table)
+{
+ return new mxRectangle(0, 0, table.offsetWidth, table.offsetHeight);
+};
+
+/**
+ * Function: updateTableWidth
+ *
+ * Updates the width of the given HTML table.
+ */
+mxText.prototype.updateTableWidth = function(table)
+{
+ var td = table.firstChild.firstChild.firstChild;
+
+ // Word-wrap for vertices (not edges) and only if not
+ // just getting the bounding box in SVG
+ if (this.wrap && this.bounds.width > 0 && this.dialect != mxConstants.DIALECT_SVG)
+ {
+ // Makes sure the label is not wrapped when measuring full length
+ td.style.whiteSpace = 'nowrap';
+ var size = this.getTableSize(table);
+ var space = Math.min(size.width, ((this.horizontal || mxUtils.isVml(this.node)) ?
+ this.bounds.width : this.bounds.height) / this.scale);
+
+ // Opera needs the new width to be scaled
+ if (mxClient.IS_OP)
+ {
+ space *= this.scale;
+ }
+
+ table.style.width = Math.round(space) + 'px';
+ td.style.whiteSpace = 'normal';
+ }
+ else
+ {
+ table.style.width = '';
+ }
+
+ if (!this.wrap)
+ {
+ td.style.whiteSpace = 'nowrap';
+ }
+ else
+ {
+ td.style.whiteSpace = 'normal';
+ }
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.redrawVml = function()
+{
+ if (this.node.nodeName == 'g')
+ {
+ this.redrawForeignObject();
+ }
+ else if (mxUtils.isVml(this.node))
+ {
+ this.redrawTextbox();
+ }
+ else
+ {
+ this.redrawHtmlTable();
+ }
+};
+
+/**
+ * Function: redrawTextbox
+ *
+ * Redraws the textbox for this text. This is only used in IE in exact
+ * rendering mode.
+ */
+mxText.prototype.redrawTextbox = function()
+{
+ // Gets VML textbox
+ var textbox = this.node;
+
+ // Creates HTML container on the fly
+ if (textbox.firstChild == null)
+ {
+ textbox.appendChild(this.createHtmlTable());
+ }
+
+ // Updates the table style and value
+ var table = textbox.firstChild;
+ this.updateHtmlTable(table);
+ this.updateTableWidth(table);
+
+ // Opacity
+ if (this.opacity != null)
+ {
+ mxUtils.setOpacity(table, this.opacity);
+ }
+
+ table.style.filter = '';
+ textbox.inset = '0px,0px,0px,0px';
+
+ if (this.overflow != 'fill')
+ {
+ // Only tables can be used to work out the actual size of the markup
+ var size = this.getTableSize(table);
+ var w = size.width * this.scale;
+ var h = size.height * this.scale;
+ var offset = this.getOffset(this.bounds.width, this.bounds.height, w, h);
+
+ // Rotates the label (IE only)
+ if (!this.horizontal)
+ {
+ table.style.filter = this.ieVerticalFilter;
+ }
+
+ // Adds horizontal/vertical spacing
+ var spacing = this.getSpacing();
+ var x = this.bounds.x - offset.x + spacing.x * this.scale;
+ var y = this.bounds.y - offset.y + spacing.y * this.scale;
+
+ // Textboxes are always relative to their parent shape's top, left corner so
+ // we use the inset for absolute positioning as they allow negative values
+ // except for edges where the bounds are used to find the shape center
+ var x0 = this.bounds.x;
+ var y0 = this.bounds.y;
+ var ow = this.bounds.width;
+ var oh = this.bounds.height;
+
+ // Insets are given as left, top, right, bottom
+ if (this.horizontal)
+ {
+ var tx = Math.round(x - x0);
+ var ty = Math.round(y - y0);
+
+ var r = Math.min(0, Math.round(x0 + ow - x - w - 1));
+ var b = Math.min(0, Math.round(y0 + oh - y - h - 1));
+ textbox.inset = tx + 'px,' + ty + 'px,' + r + 'px,' + b + 'px';
+ }
+ else
+ {
+ var t = 0;
+ var l = 0;
+ var r = 0;
+ var b = 0;
+
+ if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ t = (oh - w) / 2;
+ b = t;
+ }
+ else if (this.align == mxConstants.ALIGN_LEFT)
+ {
+ t = oh - w;
+ }
+ else
+ {
+ b = oh - w;
+ }
+
+ if (this.valign == mxConstants.ALIGN_MIDDLE)
+ {
+ l = (ow - h) / 2;
+ r = l;
+ }
+ else if (this.valign == mxConstants.ALIGN_BOTTOM)
+ {
+ l = ow - h;
+ }
+ else
+ {
+ r = ow - h;
+ }
+
+ textbox.inset = l + 'px,' + t + 'px,' + r + 'px,' + b + 'px';
+ }
+
+ textbox.style.zoom = this.scale;
+
+ // Clipping
+ if (this.clipped && this.bounds.width > 0 && this.bounds.height > 0)
+ {
+ this.boundingBox = this.bounds.clone();
+ var dx = Math.round(x0 - x);
+ var dy = Math.round(y0 - y);
+
+ textbox.style.clip = 'rect(' + (dy / this.scale) + ' ' +
+ ((dx + this.bounds.width) / this.scale) + ' ' +
+ ((dy + this.bounds.height) / this.scale) + ' ' +
+ (dx / this.scale) + ')';
+ }
+ else
+ {
+ this.boundingBox = new mxRectangle(x, y, w, h);
+ }
+ }
+ else
+ {
+ this.boundingBox = this.bounds.clone();
+ }
+};
+
+/**
+ * Function: redrawHtmlTable
+ *
+ * Redraws the HTML table. This is used for HTML labels in all modes except
+ * exact in IE and if NO_FO is false for the browser.
+ */
+mxText.prototype.redrawHtmlTable = function()
+{
+ if (isNaN(this.bounds.x) || isNaN(this.bounds.y) ||
+ isNaN(this.bounds.width) || isNaN(this.bounds.height))
+ {
+ return;
+ }
+
+ // Gets table
+ var table = this.node;
+ var td = table.firstChild.firstChild.firstChild;
+
+ // Un-rotates for computing the actual size
+ // TODO: Check if the result can be tweaked instead in getActualSize
+ // and only do this if actual rotation did change
+ var oldBrowser = false;
+ var fallbackScale = 1;
+
+ if (mxClient.IS_IE)
+ {
+ table.style.removeAttribute('filter');
+ }
+ else if (mxClient.IS_SF || mxClient.IS_GC)
+ {
+ table.style.WebkitTransform = '';
+ }
+ else if (mxClient.IS_MT)
+ {
+ table.style.MozTransform = '';
+ td.style.MozTransform = '';
+ }
+ else
+ {
+ if (mxClient.IS_OT)
+ {
+ table.style.OTransform = '';
+ }
+
+ fallbackScale = this.scale;
+ oldBrowser = true;
+ }
+
+ // Resets the current zoom for text measuring
+ td.style.zoom = '';
+
+ // Updates the table style and value
+ this.updateHtmlTable(table, fallbackScale);
+ this.updateTableWidth(table);
+
+ // Opacity
+ if (this.opacity != null)
+ {
+ mxUtils.setOpacity(table, this.opacity);
+ }
+
+ // Resets the bounds for computing the actual size
+ table.style.left = '';
+ table.style.top = '';
+ table.style.height = '';
+
+ // Workaround for multiple zoom even if CSS style is reset here
+ var currentZoom = parseFloat(td.style.zoom) || 1;
+
+ // Only tables can be used to work out the actual size of the markup
+ // NOTE: offsetWidth and offsetHeight are very slow in quirks and IE 8 standards mode
+ var w = this.bounds.width;
+ var h = this.bounds.height;
+
+ var ignoreStringSize = this.forceIgnoreStringSize || this.overflow == 'fill' ||
+ (this.align == mxConstants.ALIGN_LEFT && this.background == null && this.border == null);
+
+ if (!ignoreStringSize)
+ {
+ var size = this.getTableSize(table);
+ w = size.width / currentZoom;
+ h = size.height / currentZoom;
+ }
+
+ var offset = this.getOffset(this.bounds.width / this.scale,
+ this.bounds.height / this.scale, w, h,
+ oldBrowser || this.horizontal);
+
+ // Adds horizontal/vertical spacing
+ var spacing = this.getSpacing(oldBrowser || this.horizontal);
+ var x = this.bounds.x / this.scale - offset.x + spacing.x;
+ var y = this.bounds.y / this.scale - offset.y + spacing.y;
+
+ // Updates the table bounds and stores the scale to be used for
+ // defining the table width and height, as well as an offset
+ var s = this.scale;
+ var s2 = 1;
+ var shiftX = 0;
+ var shiftY = 0;
+
+ // Rotates the label and adds offset
+ if (!this.horizontal)
+ {
+ if (mxClient.IS_IE && mxClient.IS_SVG)
+ {
+ table.style.msTransform = 'rotate(' + this.verticalTextDegree + 'deg)';
+ }
+ else if (mxClient.IS_IE)
+ {
+ table.style.filter = this.ieVerticalFilter;
+ shiftX = (w - h) / 2;
+ shiftY = -shiftX;
+ }
+ else if (mxClient.IS_SF || mxClient.IS_GC)
+ {
+ table.style.WebkitTransform = 'rotate(' + this.verticalTextDegree + 'deg)';
+ }
+ else if (mxClient.IS_OT)
+ {
+ table.style.OTransform = 'rotate(' + this.verticalTextDegree + 'deg)';
+ }
+ else if (mxClient.IS_MT)
+ {
+ // Firefox paints background and border only if background is on TD
+ // and border is on TABLE and both are rotated, just the TD with a
+ // rotation of zero (don't remove the 0-rotate CSS style)
+ table.style.MozTransform = 'rotate(' + this.verticalTextDegree + 'deg)';
+ td.style.MozTransform = 'rotate(0deg)';
+
+ s2 = 1 / this.scale;
+ s = 1;
+ }
+ }
+
+ // Sets the zoom
+ var correction = true;
+
+ if (mxClient.IS_MT || oldBrowser)
+ {
+ if (mxClient.IS_MT)
+ {
+ table.style.MozTransform += ' scale(' + this.scale + ')';
+ s2 = 1 / this.scale;
+ }
+ else if (mxClient.IS_OT)
+ {
+ td.style.OTransform = 'scale(' + this.scale + ')';
+ table.style.borderWidth = Math.round(this.scale * parseInt(table.style.borderWidth)) + 'px';
+ }
+ }
+ else if (!oldBrowser)
+ {
+ // Workaround for unsupported zoom CSS in IE9 standards mode
+ if (document.documentMode >= 9)
+ {
+ td.style.msTransform = 'scale(' + this.scale + ')';
+ }
+ // Uses transform in Webkit for better HTML scaling
+ else if (mxClient.IS_SF || mxClient.IS_GC)
+ {
+ td.style.WebkitTransform = 'scale(' + this.scale + ')';
+ }
+ else
+ {
+ td.style.zoom = this.scale;
+
+ // Fixes scaling of border width
+ if (table.style.borderWidth != '' && document.documentMode != 8)
+ {
+ table.style.borderWidth = Math.round(this.scale * parseInt(table.style.borderWidth)) + 'px';
+ }
+
+ // Workaround for wrong scale in IE8 standards mode
+ if (document.documentMode == 8 || !mxClient.IS_IE)
+ {
+ s = 1;
+ }
+
+ correction = false;
+ }
+ }
+
+ if (correction)
+ {
+ // Workaround for scaled TD position
+ shiftX = (this.scale - 1) * w / (2 * this.scale);
+ shiftY = (this.scale - 1) * h / (2 * this.scale);
+ s = 1;
+ }
+
+ if (this.overflow != 'fill')
+ {
+ var rect = new mxRectangle(Math.round((x + shiftX) * this.scale),
+ Math.round((y + shiftY) * this.scale), Math.round(w * s), Math.round(h * s));
+ table.style.left = rect.x + 'px';
+ table.style.top = rect.y + 'px';
+ table.style.width = rect.width + 'px';
+ table.style.height = rect.height + 'px';
+
+ // Workaround for wrong scale in border and background rendering for table and td in IE8/9 standards mode
+ if ((this.background != null || this.border != null) && document.documentMode >= 8)
+ {
+ var html = (this.replaceLinefeeds) ? this.value.replace(/\n/g, '<br/>') : this.value;
+ td.innerHTML = '<div style="padding:' + this.labelPadding + 'px;background:' + td.style.background + ';border:' + table.style.border + '">' + html + '</div>';
+ td.style.padding = '0px';
+ td.style.background = '';
+ table.style.border = '';
+ }
+
+ // Clipping
+ if (this.clipped && this.bounds.width > 0 && this.bounds.height > 0)
+ {
+ this.boundingBox = this.bounds.clone();
+
+ // Clipping without rotation or for older browsers
+ if (this.horizontal || (oldBrowser && !mxClient.IS_OT))
+ {
+ var dx = Math.max(0, offset.x * s);
+ var dy = Math.max(0, offset.y * s);
+
+ // TODO: Fix clipping for Opera
+ table.style.clip = 'rect(' + (dy) + 'px ' + (dx + this.bounds.width * s2) +
+ 'px ' + (dy + this.bounds.height * s2) + 'px ' + (dx) + 'px)';
+ }
+ else
+ {
+ // Workaround for IE clip using top, right, bottom, left (un-rotated)
+ if (mxClient.IS_IE)
+ {
+ var uw = this.bounds.width;
+ var uh = this.bounds.height;
+ var dx = 0;
+ var dy = 0;
+
+ if (this.align == mxConstants.ALIGN_LEFT)
+ {
+ dx = Math.max(0, w - uh / this.scale) * this.scale;
+ }
+ else if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ dx = Math.max(0, w - uh / this.scale) * this.scale / 2;
+ }
+
+ if (this.valign == mxConstants.ALIGN_BOTTOM)
+ {
+ dy = Math.max(0, h - uw / this.scale) * this.scale;
+ }
+ else if (this.valign == mxConstants.ALIGN_MIDDLE)
+ {
+ dy = Math.max(0, h - uw / this.scale) * this.scale / 2;
+ }
+
+ table.style.clip = 'rect(' + (dx) + 'px ' + (dy + uw - 1) +
+ 'px ' + (dx + uh - 1) + 'px ' + (dy) + 'px)';
+ }
+ else
+ {
+ var uw = this.bounds.width / this.scale;
+ var uh = this.bounds.height / this.scale;
+
+ if (mxClient.IS_OT)
+ {
+ uw = this.bounds.width;
+ uh = this.bounds.height;
+ }
+
+ var dx = 0;
+ var dy = 0;
+
+ if (this.align == mxConstants.ALIGN_RIGHT)
+ {
+ dx = Math.max(0, w - uh);
+ }
+ else if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ dx = Math.max(0, w - uh) / 2;
+ }
+
+ if (this.valign == mxConstants.ALIGN_BOTTOM)
+ {
+ dy = Math.max(0, h - uw);
+ }
+ else if (this.valign == mxConstants.ALIGN_MIDDLE)
+ {
+ dy = Math.max(0, h - uw) / 2;
+ }
+
+ if (mxClient.IS_GC || mxClient.IS_SF)
+ {
+ dx *= this.scale;
+ dy *= this.scale;
+ uw *= this.scale;
+ uh *= this.scale;
+ }
+
+ table.style.clip = 'rect(' + (dy) + ' ' + (dx + uh) +
+ ' ' + (dy + uw) + ' ' + (dx) + ')';
+ }
+ }
+ }
+ else
+ {
+ this.boundingBox = rect;
+ }
+ }
+ else
+ {
+ this.boundingBox = this.bounds.clone();
+
+ if (document.documentMode >= 9 || mxClient.IS_SVG)
+ {
+ table.style.left = Math.round(this.bounds.x + this.scale / 2 + shiftX) + 'px';
+ table.style.top = Math.round(this.bounds.y + this.scale / 2 + shiftY) + 'px';
+ table.style.width = Math.round((this.bounds.width - this.scale) / this.scale) + 'px';
+ table.style.height = Math.round((this.bounds.height - this.scale) / this.scale) + 'px';
+ }
+ else
+ {
+ s = (document.documentMode == 8) ? this.scale : 1;
+ table.style.left = Math.round(this.bounds.x + this.scale / 2) + 'px';
+ table.style.top = Math.round(this.bounds.y + this.scale / 2) + 'px';
+ table.style.width = Math.round((this.bounds.width - this.scale) / s) + 'px';
+ table.style.height = Math.round((this.bounds.height - this.scale) / s) + 'px';
+ }
+ }
+};
+
+/**
+ * Function: getVerticalOffset
+ *
+ * Returns the factors for the offset to be added to the text vertical
+ * text rotation. This implementation returns (offset.y, -offset.x).
+ */
+mxText.prototype.getVerticalOffset = function(offset)
+{
+ return new mxPoint(offset.y, -offset.x);
+};
+
+/**
+ * Function: redrawForeignObject
+ *
+ * Redraws the foreign object for this text.
+ */
+mxText.prototype.redrawForeignObject = function()
+{
+ // Gets SVG group with foreignObject
+ var group = this.node;
+ var fo = group.firstChild;
+
+ // Searches the table which appears behind the background
+ while (fo == this.backgroundNode)
+ {
+ fo = fo.nextSibling;
+ }
+
+ var body = fo.firstChild;
+
+ // Creates HTML container on the fly
+ if (body.firstChild == null)
+ {
+ body.appendChild(this.createHtmlTable());
+ }
+
+ // Updates the table style and value
+ var table = body.firstChild;
+ this.updateHtmlTable(table);
+
+ // Workaround for bug in Google Chrome where the text is moved to origin if opacity
+ // is set on the table, so we set the opacity on the foreignObject instead.
+ if (this.opacity != null)
+ {
+ fo.setAttribute('opacity', this.opacity / 100);
+ }
+
+ // Workaround for table background not appearing above the shape that is
+ // behind the label in Safari. To solve this, we add a background rect that
+ // paints the background instead.
+ if (mxClient.IS_SF)
+ {
+ table.style.borderStyle = 'none';
+ table.firstChild.firstChild.firstChild.style.background = '';
+
+ if (this.backgroundNode == null && (this.background != null || this.border != null))
+ {
+ this.backgroundNode = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ group.insertBefore(this.backgroundNode, group.firstChild);
+ }
+ else if (this.backgroundNode != null && this.background == null && this.border == null)
+ {
+ this.backgroundNode.parentNode.removeChild(this.backgroundNode);
+ this.backgroundNode = null;
+ }
+
+ if (this.backgroundNode != null)
+ {
+ if (this.background != null)
+ {
+ this.backgroundNode.setAttribute('fill', this.background);
+ }
+ else
+ {
+ this.backgroundNode.setAttribute('fill', 'none');
+ }
+
+ if (this.border != null)
+ {
+ this.backgroundNode.setAttribute('stroke', this.border);
+ }
+ else
+ {
+ this.backgroundNode.setAttribute('stroke', 'none');
+ }
+ }
+ }
+
+ var tr = '';
+
+ if (this.overflow != 'fill')
+ {
+ // Resets the bounds for computing the actual size
+ fo.removeAttribute('width');
+ fo.removeAttribute('height');
+ fo.style.width = '';
+ fo.style.height = '';
+ fo.style.clip = '';
+
+ // Workaround for size of table not updated if inside foreignObject
+ if (this.wrap || (!mxClient.IS_GC && !mxClient.IS_SF))
+ {
+ document.body.appendChild(table);
+ }
+
+ this.updateTableWidth(table);
+
+ // Only tables can be used to work out the actual size of the markup
+ var size = this.getTableSize(table);
+ var w = size.width;
+ var h = size.height;
+
+ if (table.parentNode != body)
+ {
+ body.appendChild(table);
+ }
+
+ // Adds horizontal/vertical spacing
+ var spacing = this.getSpacing();
+
+ var x = this.bounds.x / this.scale + spacing.x;
+ var y = this.bounds.y / this.scale + spacing.y;
+ var uw = this.bounds.width / this.scale;
+ var uh = this.bounds.height / this.scale;
+ var offset = this.getOffset(uw, uh, w, h);
+
+ // Rotates the label and adds offset
+ if (this.horizontal)
+ {
+ x -= offset.x;
+ y -= offset.y;
+
+ tr = 'scale(' + this.scale + ')';
+ }
+ else
+ {
+ var x0 = x + w / 2;
+ var y0 = y + h / 2;
+
+ tr = 'scale(' + this.scale + ') rotate(' + this.verticalTextDegree + ' ' + x0 + ' ' + y0 + ')';
+
+ var tmp = this.getVerticalOffset(offset);
+ x += tmp.x;
+ y += tmp.y;
+ }
+
+ // Must use translate instead of x- and y-attribute on FO for iOS
+ tr += ' translate(' + x + ' ' + y + ')';
+
+ // Updates the bounds of the background node in Webkit
+ if (this.backgroundNode != null)
+ {
+ this.backgroundNode.setAttribute('width', w);
+ this.backgroundNode.setAttribute('height', h);
+ }
+
+ // Updates the foreignObject size
+ fo.setAttribute('width', w);
+ fo.setAttribute('height', h);
+
+ // Clipping
+ // TODO: Fix/check clipping for foreignObjects in Chrome 5.0 - if clipPath
+ // is used in the group then things can no longer be moved around
+ if (this.clipped && this.bounds.width > 0 && this.bounds.height > 0)
+ {
+ this.boundingBox = this.bounds.clone();
+ var dx = Math.max(0, offset.x);
+ var dy = Math.max(0, offset.y);
+
+ if (this.horizontal)
+ {
+ fo.style.clip = 'rect(' + dy + 'px,' + (dx + uw) +
+ 'px,' + (dy + uh) + 'px,' + (dx) + 'px)';
+ }
+ else
+ {
+ var dx = 0;
+ var dy = 0;
+
+ if (this.align == mxConstants.ALIGN_RIGHT)
+ {
+ dx = Math.max(0, w - uh);
+ }
+ else if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ dx = Math.max(0, w - uh) / 2;
+ }
+
+ if (this.valign == mxConstants.ALIGN_BOTTOM)
+ {
+ dy = Math.max(0, h - uw);
+ }
+ else if (this.valign == mxConstants.ALIGN_MIDDLE)
+ {
+ dy = Math.max(0, h - uw) / 2;
+ }
+
+ fo.style.clip = 'rect(' + (dy) + 'px,' + (dx + uh) +
+ 'px,' + (dy + uw) + 'px,' + (dx) + 'px)';
+ }
+
+ // Clipping for the background node in Chrome
+ if (this.backgroundNode != null)
+ {
+ x = this.bounds.x / this.scale;
+ y = this.bounds.y / this.scale;
+
+ if (!this.horizontal)
+ {
+ x += (h + w) / 2 - uh;
+ y += (h - w) / 2;
+
+ var tmp = uw;
+ uw = uh;
+ uh = tmp;
+ }
+
+ // No clipping in Chome available due to bug
+ if (!mxClient.IS_GC)
+ {
+ var clip = this.getSvgClip(this.node.ownerSVGElement, x, y, uw, uh);
+
+ if (clip != this.clip)
+ {
+ this.releaseSvgClip();
+ this.clip = clip;
+ clip.refCount++;
+ }
+
+ this.backgroundNode.setAttribute('clip-path', 'url(#' + clip.getAttribute('id') + ')');
+ }
+ }
+ }
+ else
+ {
+ // Removes clipping from background and cleans up the clip
+ this.releaseSvgClip();
+
+ if (this.backgroundNode != null)
+ {
+ this.backgroundNode.removeAttribute('clip-path');
+ }
+
+ if (this.horizontal)
+ {
+ this.boundingBox = new mxRectangle(x * this.scale, y * this.scale, w * this.scale, h * this.scale);
+ }
+ else
+ {
+ this.boundingBox = new mxRectangle(x * this.scale, y * this.scale, h * this.scale, w * this.scale);
+ }
+ }
+ }
+ else
+ {
+ this.boundingBox = this.bounds.clone();
+
+ var s = this.scale;
+ var w = this.bounds.width / s;
+ var h = this.bounds.height / s;
+
+ // Updates the foreignObject and table bounds
+ fo.setAttribute('width', w);
+ fo.setAttribute('height', h);
+ table.style.width = w + 'px';
+ table.style.height = h + 'px';
+
+ // Updates the bounds of the background node in Webkit
+ if (this.backgroundNode != null)
+ {
+ this.backgroundNode.setAttribute('width', table.clientWidth);
+ this.backgroundNode.setAttribute('height', table.offsetHeight);
+ }
+
+ // Must use translate instead of x- and y-attribute on FO for iOS
+ tr = 'scale(' + s + ') translate(' + (this.bounds.x / s) +
+ ' ' + (this.bounds.y / s) + ')';
+
+ if (!this.wrap)
+ {
+ var td = table.firstChild.firstChild.firstChild;
+ td.style.whiteSpace = 'nowrap';
+ }
+ }
+
+ group.setAttribute('transform', tr);
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxText.prototype.createSvg = function()
+{
+ // Creates a group so that shapes inside are rendered properly, if this is
+ // a text node then the background rectangle is not rendered in Webkit.
+ var node = document.createElementNS(mxConstants.NS_SVG, 'g');
+
+ var uline = this.isStyleSet(mxConstants.FONT_UNDERLINE) ? 'underline' : 'none';
+ var weight = this.isStyleSet(mxConstants.FONT_BOLD) ? 'bold' : 'normal';
+ var s = this.isStyleSet(mxConstants.FONT_ITALIC) ? 'italic' : null;
+
+ // Underline is not implemented in FF, see
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=317196
+ node.setAttribute('text-decoration', uline);
+ node.setAttribute('font-family', this.family);
+ node.setAttribute('font-weight', weight);
+ node.setAttribute('font-size', Math.round(this.size * this.scale) + 'px');
+ node.setAttribute('fill', this.color);
+ var align = (this.align == mxConstants.ALIGN_RIGHT) ? 'end' :
+ (this.align == mxConstants.ALIGN_CENTER) ? 'middle' :
+ 'start';
+ node.setAttribute('text-anchor', align);
+
+ if (s != null)
+ {
+ node.setAttribute('font-style', s);
+ }
+
+ // Adds a rectangle for the background color
+ if (this.background != null || this.border != null)
+ {
+ this.backgroundNode = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ this.backgroundNode.setAttribute('shape-rendering', 'crispEdges');
+
+ if (this.background != null)
+ {
+ this.backgroundNode.setAttribute('fill', this.background);
+ }
+ else
+ {
+ this.backgroundNode.setAttribute('fill', 'none');
+ }
+
+ if (this.border != null)
+ {
+ this.backgroundNode.setAttribute('stroke', this.border);
+ }
+ else
+ {
+ this.backgroundNode.setAttribute('stroke', 'none');
+ }
+ }
+
+ this.updateSvgValue(node);
+
+ return node;
+};
+
+/**
+ * Updates the text represented by the SVG DOM nodes.
+ */
+mxText.prototype.updateSvgValue = function(node)
+{
+ if (this.currentValue != this.value)
+ {
+ // Removes all existing children
+ while (node.firstChild != null)
+ {
+ node.removeChild(node.firstChild);
+ }
+
+ if (this.value != null)
+ {
+ // Adds tspan elements for the lines
+ var uline = this.isStyleSet(mxConstants.FONT_UNDERLINE) ? 'underline' : 'none';
+ var lines = this.value.split('\n');
+
+ // Workaround for empty lines breaking the return value of getBBox
+ // for the enclosing g element so we avoid adding empty lines
+ // but still count them as a linefeed
+ this.textNodes = new Array(lines.length);
+
+ for (var i = 0; i < lines.length; i++)
+ {
+ if (!this.isEmptyString(lines[i]))
+ {
+ var tspan = this.createSvgSpan(lines[i]);
+ node.appendChild(tspan);
+ this.textNodes[i] = tspan;
+
+ // Requires either 'inherit' in Webkit or explicit setting
+ // to work in Webkit and IE9 standards mode. Both, inherit
+ // and underline do not work in FF. This is a known bug in
+ // FF (see above).
+ tspan.setAttribute('text-decoration', uline);
+ }
+ else
+ {
+ this.textNodes[i] = null;
+ }
+ }
+ }
+
+ this.currentValue = this.value;
+ }
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.redrawSvg = function()
+{
+ if (this.node.nodeName == 'foreignObject')
+ {
+ this.redrawHtml();
+
+ return;
+ }
+
+ var fontSize = Math.round(this.size * this.scale);
+
+ if (fontSize <= 0)
+ {
+ this.node.setAttribute('visibility', 'hidden');
+ }
+ else
+ {
+ this.node.removeAttribute('visibility');
+ }
+
+ this.updateSvgValue(this.node);
+ this.node.setAttribute('font-size', fontSize + 'px');
+
+ if (this.opacity != null)
+ {
+ // Improves opacity performance in Firefox
+ this.node.setAttribute('fill-opacity', this.opacity/100);
+ this.node.setAttribute('stroke-opacity', this.opacity/100);
+ }
+
+ // Workaround to avoid the use of getBBox to find the size
+ // of the label. A temporary HTML table is created instead.
+ var previous = this.value;
+ var table = this.createHtmlTable();
+
+ // Makes sure the table is updated and replaces all HTML entities
+ this.lastValue = null;
+ this.value = mxUtils.htmlEntities(this.value, false);
+ this.updateHtmlTable(table);
+
+ // Adds the table to the DOM to find the actual size
+ document.body.appendChild(table);
+ var w = table.offsetWidth * this.scale;
+ var h = table.offsetHeight * this.scale;
+
+ // Cleans up the DOM and restores the original value
+ table.parentNode.removeChild(table);
+ this.value = previous;
+
+ // Sets the bounding box for the unclipped case so that
+ // the full background can be painted using it, the initial
+ // value for dx and the +4 in the width below are for
+ // error correction of the HTML and SVG text width
+ var dx = 2 * this.scale;
+
+ if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ dx += w / 2;
+ }
+ else if (this.align == mxConstants.ALIGN_RIGHT)
+ {
+ dx += w;
+ }
+
+ var dy = Math.round(fontSize * 1.3);
+ var childCount = this.node.childNodes.length;
+ var lineCount = (this.textNodes != null) ? this.textNodes.length : 0;
+
+ if (this.backgroundNode != null)
+ {
+ childCount--;
+ }
+
+ var x = this.bounds.x;
+ var y = this.bounds.y;
+
+ x += (this.align == mxConstants.ALIGN_RIGHT) ?
+ ((this.horizontal) ? this.bounds.width : this.bounds.height)-
+ this.spacingRight * this.scale :
+ (this.align == mxConstants.ALIGN_CENTER) ?
+ this.spacingLeft * this.scale +
+ (((this.horizontal) ? this.bounds.width : this.bounds.height) -
+ this.spacingLeft * this.scale - this.spacingRight * this.scale) / 2 :
+ this.spacingLeft * this.scale + 1;
+
+ // Makes sure the alignment is like in VML and HTML
+ y += (this.valign == mxConstants.ALIGN_BOTTOM) ?
+ ((this.horizontal) ? this.bounds.height : this.bounds.width) -
+ (lineCount - 1) * dy - this.spacingBottom * this.scale - 4 :
+ (this.valign == mxConstants.ALIGN_MIDDLE) ?
+ (this.spacingTop * this.scale +
+ ((this.horizontal) ? this.bounds.height : this.bounds.width) -
+ this.spacingBottom * this.scale -
+ (lineCount - 1.5) * dy) / 2 :
+ this.spacingTop * this.scale + dy;
+
+ if (this.overflow == 'fill')
+ {
+ if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ x = Math.max(this.bounds.x + w / 2, x);
+ }
+
+ y = Math.max(this.bounds.y + fontSize, y);
+
+ this.boundingBox = new mxRectangle(x - dx, y - dy,
+ w + 4 * this.scale, h + 1 * this.scale);
+ this.boundingBox.x = Math.min(this.bounds.x, this.boundingBox.x);
+ this.boundingBox.y = Math.min(this.bounds.y, this.boundingBox.y);
+ this.boundingBox.width = Math.max(this.bounds.width, this.boundingBox.width);
+ this.boundingBox.height = Math.max(this.bounds.height, this.boundingBox.height);
+ }
+ else
+ {
+ this.boundingBox = new mxRectangle(x - dx, y - dy,
+ w + 4 * this.scale, h + 1 * this.scale);
+ }
+
+ if (!this.horizontal)
+ {
+ var cx = this.bounds.x + this.bounds.width / 2;
+ var cy = this.bounds.y + this.bounds.height / 2;
+
+ var offsetX = (this.bounds.width - this.bounds.height) / 2;
+ var offsetY = (this.bounds.height - this.bounds.width) / 2;
+
+ this.node.setAttribute('transform',
+ 'rotate(' + this.verticalTextDegree + ' ' + cx + ' ' + cy + ') ' +
+ 'translate(' + (-offsetY) + ' ' + (-offsetX) + ')');
+ }
+
+ // TODO: Font-shadow
+ this.redrawSvgTextNodes(x, y, dy);
+
+ /*
+ * FIXME: Bounding box is not rotated. This seems to be a problem for
+ * all vertical text boxes. Workaround is in mxImageExport.
+ if (!this.horizontal)
+ {
+ var b = this.bounds.y + this.bounds.height;
+ var cx = this.boundingBox.getCenterX() - this.bounds.x;
+ var cy = this.boundingBox.getCenterY() - this.bounds.y;
+
+ var y = b - cx - this.bounds.height / 2;
+ this.boundingBox.x = this.bounds.x + cy - this.boundingBox.width / 2;
+ this.boundingBox.y = y;
+ }
+ */
+
+ // Updates the bounds of the background node if one exists
+ if (this.value.length > 0 && this.backgroundNode != null && this.node.firstChild != null)
+ {
+ if (this.node.firstChild != this.backgroundNode)
+ {
+ this.node.insertBefore(this.backgroundNode, this.node.firstChild);
+ }
+
+ // FIXME: For larger font sizes the linespacing between HTML and SVG
+ // seems to be different and hence the bounding box isn't accurate.
+ // Also in Firefox the background box is slighly offset.
+ this.backgroundNode.setAttribute('x', this.boundingBox.x + this.scale / 2 + 1 * this.scale);
+ this.backgroundNode.setAttribute('y', this.boundingBox.y + this.scale / 2 + 2 * this.scale - this.labelPadding);
+ this.backgroundNode.setAttribute('width', this.boundingBox.width - this.scale - 2 * this.scale);
+ this.backgroundNode.setAttribute('height', this.boundingBox.height - this.scale);
+
+ var strokeWidth = Math.round(Math.max(1, this.scale));
+ this.backgroundNode.setAttribute('stroke-width', strokeWidth);
+ }
+
+ // Adds clipping and updates the bounding box
+ if (this.clipped && this.bounds.width > 0 && this.bounds.height > 0)
+ {
+ this.boundingBox = this.bounds.clone();
+
+ if (!this.horizontal)
+ {
+ this.boundingBox.width = this.bounds.height;
+ this.boundingBox.height = this.bounds.width;
+ }
+
+ x = this.bounds.x;
+ y = this.bounds.y;
+
+ if (this.horizontal)
+ {
+ w = this.bounds.width;
+ h = this.bounds.height;
+ }
+ else
+ {
+ w = this.bounds.height;
+ h = this.bounds.width;
+ }
+
+ var clip = this.getSvgClip(this.node.ownerSVGElement, x, y, w, h);
+
+ if (clip != this.clip)
+ {
+ this.releaseSvgClip();
+ this.clip = clip;
+ clip.refCount++;
+ }
+
+ this.node.setAttribute('clip-path', 'url(#' + clip.getAttribute('id') + ')');
+ }
+ else
+ {
+ this.releaseSvgClip();
+ this.node.removeAttribute('clip-path');
+ }
+};
+
+/**
+ * Function: redrawSvgTextNodes
+ *
+ * Hook to update the position of the SVG text nodes.
+ */
+mxText.prototype.redrawSvgTextNodes = function(x, y, dy)
+{
+ if (this.textNodes != null)
+ {
+ var currentY = y;
+
+ for (var i = 0; i < this.textNodes.length; i++)
+ {
+ var node = this.textNodes[i];
+
+ if (node != null)
+ {
+ node.setAttribute('x', x);
+ node.setAttribute('y', currentY);
+
+ // Triggers an update in Firefox 1.5.0.x (don't add a semicolon!)
+ node.setAttribute('style', 'pointer-events: all');
+ }
+
+ currentY += dy;
+ }
+ }
+};
+
+/**
+ * Function: releaseSvgClip
+ *
+ * Releases the given SVG clip removing it from the DOM if required.
+ */
+mxText.prototype.releaseSvgClip = function()
+{
+ if (this.clip != null)
+ {
+ this.clip.refCount--;
+
+ if (this.clip.refCount == 0)
+ {
+ this.clip.parentNode.removeChild(this.clip);
+ }
+
+ this.clip = null;
+ }
+};
+
+/**
+ * Function: getSvgClip
+ *
+ * Returns a new or existing SVG clip path which is a descendant of the given
+ * SVG node with a unique ID.
+ */
+mxText.prototype.getSvgClip = function(svg, 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;
+
+ // Quick access
+ if (this.clip != null && this.clip.ident == id)
+ {
+ return this.clip;
+ }
+
+ var counter = 0;
+ var tmp = id + '-' + counter;
+ var clip = document.getElementById(tmp);
+
+ // Tries to find an existing clip in the given SVG
+ while (clip != null)
+ {
+ if (clip.ownerSVGElement == svg)
+ {
+ return clip;
+ }
+
+ counter++;
+ tmp = id + '-' + counter;
+ clip = document.getElementById(tmp);
+ }
+
+ // Creates a new clip node and adds it to the DOM
+ if (clip != null)
+ {
+ clip = clip.cloneNode(true);
+ counter++;
+ }
+ else
+ {
+ clip = document.createElementNS(mxConstants.NS_SVG, 'clipPath');
+
+ var rect = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ rect.setAttribute('x', x);
+ rect.setAttribute('y', y);
+ rect.setAttribute('width', w);
+ rect.setAttribute('height', h);
+
+ clip.appendChild(rect);
+ }
+
+ clip.setAttribute('id', id + '-' + counter);
+ clip.ident = id; // For quick access above
+ svg.appendChild(clip);
+ clip.refCount = 0;
+
+ return clip;
+};
+
+/**
+ * Function: isEmptyString
+ *
+ * Returns true if the given string is empty or
+ * contains only whitespace.
+ */
+mxText.prototype.isEmptyString = function(text)
+{
+ return text.replace(/ /g, '').length == 0;
+};
+
+/**
+ * Function: createSvgSpan
+ *
+ * Creats an SVG tspan node for the given text.
+ */
+mxText.prototype.createSvgSpan = function(text)
+{
+ // Creates a text node since there is no enclosing text element but
+ // rather a group, which is required to render the background rectangle
+ // in Webkit. This can be changed to tspan if the enclosing node is
+ // a text but this leads to an hidden background in Webkit.
+ var node = document.createElementNS(mxConstants.NS_SVG, 'text');
+ // Needed to preserve multiple white spaces, but ignored in IE9 plus white-space:pre
+ // is ignored in HTML output for VML, so better to not use this for SVG labels
+ // node.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve')
+ // Alternative idea is to replace all spaces with &nbsp; to fix HTML in IE, but
+ // IE9/10 with SVG will still ignore the xml:space preserve tag as discussed here:
+ // http://stackoverflow.com/questions/8086292/significant-whitespace-in-svg-embedded-in-html
+ // Could replace spaces with &nbsp; in text but HTML tags must be scaped first.
+ mxUtils.write(node, text);
+
+ return node;
+};
+
+/**
+ * Function: destroy
+ *
+ * Extends destroy to remove any allocated SVG clips.
+ */
+mxText.prototype.destroy = function()
+{
+ this.releaseSvgClip();
+ mxShape.prototype.destroy.apply(this, arguments);
+};
diff --git a/src/js/shape/mxTriangle.js b/src/js/shape/mxTriangle.js
new file mode 100644
index 0000000..3a48db2
--- /dev/null
+++ b/src/js/shape/mxTriangle.js
@@ -0,0 +1,34 @@
+/**
+ * $Id: mxTriangle.js,v 1.10 2011-09-02 10:01:00 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxTriangle
+ *
+ * Implementation of the triangle shape.
+ *
+ * Constructor: mxTriangle
+ *
+ * Constructs a new triangle shape.
+ */
+function mxTriangle() { };
+
+/**
+ * Extends <mxActor>.
+ */
+mxTriangle.prototype = new mxActor();
+mxTriangle.prototype.constructor = mxTriangle;
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxTriangle.prototype.redrawPath = function(path, x, y, w, h)
+{
+ path.moveTo(0, 0);
+ path.lineTo(w, 0.5 * h);
+ path.lineTo(0, h);
+ path.close();
+};
diff --git a/src/js/util/mxAnimation.js b/src/js/util/mxAnimation.js
new file mode 100644
index 0000000..80901ef
--- /dev/null
+++ b/src/js/util/mxAnimation.js
@@ -0,0 +1,82 @@
+/**
+ * $Id: mxAnimation.js,v 1.2 2010-03-19 12:53:29 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxAnimation
+ *
+ * Implements a basic animation in JavaScript.
+ *
+ * Constructor: mxAnimation
+ *
+ * Constructs an animation.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxAnimation(delay)
+{
+ this.delay = (delay != null) ? delay : 20;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxAnimation.prototype = new mxEventSource();
+mxAnimation.prototype.constructor = mxAnimation;
+
+/**
+ * Variable: delay
+ *
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxAnimation.prototype.delay = null;
+
+/**
+ * Variable: thread
+ *
+ * Reference to the thread while the animation is running.
+ */
+mxAnimation.prototype.thread = null;
+
+/**
+ * Function: startAnimation
+ *
+ * Starts the animation by repeatedly invoking updateAnimation.
+ */
+mxAnimation.prototype.startAnimation = function()
+{
+ if (this.thread == null)
+ {
+ this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay);
+ }
+};
+
+/**
+ * Function: updateAnimation
+ *
+ * Hook for subclassers to implement the animation. Invoke stopAnimation
+ * when finished, startAnimation to resume. This is called whenever the
+ * timer fires and fires an mxEvent.EXECUTE event with no properties.
+ */
+mxAnimation.prototype.updateAnimation = function()
+{
+ this.fireEvent(new mxEventObject(mxEvent.EXECUTE));
+};
+
+/**
+ * Function: stopAnimation
+ *
+ * Stops the animation by deleting the timer and fires an <mxEvent.DONE>.
+ */
+mxAnimation.prototype.stopAnimation = function()
+{
+ if (this.thread != null)
+ {
+ window.clearInterval(this.thread);
+ this.thread = null;
+ this.fireEvent(new mxEventObject(mxEvent.DONE));
+ }
+};
diff --git a/src/js/util/mxAutoSaveManager.js b/src/js/util/mxAutoSaveManager.js
new file mode 100644
index 0000000..85c23dc
--- /dev/null
+++ b/src/js/util/mxAutoSaveManager.js
@@ -0,0 +1,213 @@
+/**
+ * $Id: mxAutoSaveManager.js,v 1.9 2010-09-16 09:10:21 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxAutoSaveManager
+ *
+ * Manager for automatically saving diagrams. The <save> hook must be
+ * implemented.
+ *
+ * Example:
+ *
+ * (code)
+ * var mgr = new mxAutoSaveManager(editor.graph);
+ * mgr.save = function()
+ * {
+ * mxLog.show();
+ * mxLog.debug('save');
+ * };
+ * (end)
+ *
+ * Constructor: mxAutoSaveManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxAutoSaveManager(graph)
+{
+ // Notifies the manager of a change
+ this.changeHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.graphModelChanged(evt.getProperty('edit').changes);
+ }
+ });
+
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxAutoSaveManager.prototype = new mxEventSource();
+mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxAutoSaveManager.prototype.graph = null;
+
+/**
+ * Variable: autoSaveDelay
+ *
+ * Minimum amount of seconds between two consecutive autosaves. Eg. a
+ * value of 1 (s) means the graph is not stored more than once per second.
+ * Default is 10.
+ */
+mxAutoSaveManager.prototype.autoSaveDelay = 10;
+
+/**
+ * Variable: autoSaveThrottle
+ *
+ * Minimum amount of seconds between two consecutive autosaves triggered by
+ * more than <autoSaveThreshhold> changes within a timespan of less than
+ * <autoSaveDelay> seconds. Eg. a value of 1 (s) means the graph is not
+ * stored more than once per second even if there are more than
+ * <autoSaveThreshold> changes within that timespan. Default is 2.
+ */
+mxAutoSaveManager.prototype.autoSaveThrottle = 2;
+
+/**
+ * Variable: autoSaveThreshold
+ *
+ * Minimum amount of ignored changes before an autosave. Eg. a value of 2
+ * means after 2 change of the graph model the autosave will trigger if the
+ * condition below is true. Default is 5.
+ */
+mxAutoSaveManager.prototype.autoSaveThreshold = 5;
+
+/**
+ * Variable: ignoredChanges
+ *
+ * Counter for ignored changes in autosave.
+ */
+mxAutoSaveManager.prototype.ignoredChanges = 0;
+
+/**
+ * Variable: lastSnapshot
+ *
+ * Used for autosaving. See <autosave>.
+ */
+mxAutoSaveManager.prototype.lastSnapshot = 0;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxAutoSaveManager.prototype.enabled = true;
+
+/**
+ * Variable: changeHandler
+ *
+ * Holds the function that handles graph model changes.
+ */
+mxAutoSaveManager.prototype.changeHandler = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxAutoSaveManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxAutoSaveManager.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxAutoSaveManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ this.graph.getModel().removeListener(this.changeHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+ }
+};
+
+/**
+ * Function: save
+ *
+ * Empty hook that is called if the graph should be saved.
+ */
+mxAutoSaveManager.prototype.save = function()
+{
+ // empty
+};
+
+/**
+ * Function: graphModelChanged
+ *
+ * Invoked when the graph model has changed.
+ */
+mxAutoSaveManager.prototype.graphModelChanged = function(changes)
+{
+ var now = new Date().getTime();
+ var dt = (now - this.lastSnapshot) / 1000;
+
+ if (dt > this.autoSaveDelay ||
+ (this.ignoredChanges >= this.autoSaveThreshold &&
+ dt > this.autoSaveThrottle))
+ {
+ this.save();
+ this.reset();
+ }
+ else
+ {
+ // Increments the number of ignored changes
+ this.ignoredChanges++;
+ }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets all counters.
+ */
+mxAutoSaveManager.prototype.reset = function()
+{
+ this.lastSnapshot = new Date().getTime();
+ this.ignoredChanges = 0;
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxAutoSaveManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/util/mxClipboard.js b/src/js/util/mxClipboard.js
new file mode 100644
index 0000000..e9fec6b
--- /dev/null
+++ b/src/js/util/mxClipboard.js
@@ -0,0 +1,144 @@
+/**
+ * $Id: mxClipboard.js,v 1.29 2010-01-02 09:45:14 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxClipboard =
+{
+ /**
+ * Class: mxClipboard
+ *
+ * Singleton that implements a clipboard for graph cells.
+ *
+ * Example:
+ *
+ * (code)
+ * mxClipboard.copy(graph);
+ * mxClipboard.paste(graph2);
+ * (end)
+ *
+ * This copies the selection cells from the graph to the
+ * clipboard and pastes them into graph2.
+ *
+ * For fine-grained control of the clipboard data the <mxGraph.canExportCell>
+ * and <mxGraph.canImportCell> functions can be overridden.
+ *
+ * Variable: STEPSIZE
+ *
+ * Defines the step size to offset the cells
+ * after each paste operation. Default is 10.
+ */
+ STEPSIZE: 10,
+
+ /**
+ * Variable: insertCount
+ *
+ * Counts the number of times the clipboard data has been inserted.
+ */
+ insertCount: 1,
+
+ /**
+ * Variable: cells
+ *
+ * Holds the array of <mxCells> currently in the clipboard.
+ */
+ cells: null,
+
+ /**
+ * Function: isEmpty
+ *
+ * Returns true if the clipboard currently has not data stored.
+ */
+ isEmpty: function()
+ {
+ return mxClipboard.cells == null;
+ },
+
+ /**
+ * Function: cut
+ *
+ * Cuts the given array of <mxCells> from the specified graph.
+ * If cells is null then the selection cells of the graph will
+ * be used. Returns the cells that have been cut from the graph.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> that contains the cells to be cut.
+ * cells - Optional array of <mxCells> to be cut.
+ */
+ cut: function(graph, cells)
+ {
+ cells = mxClipboard.copy(graph, cells);
+ mxClipboard.insertCount = 0;
+ mxClipboard.removeCells(graph, cells);
+
+ return cells;
+ },
+
+ /**
+ * Function: removeCells
+ *
+ * Hook to remove the given cells from the given graph after
+ * a cut operation.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> that contains the cells to be cut.
+ * cells - Array of <mxCells> to be cut.
+ */
+ removeCells: function(graph, cells)
+ {
+ graph.removeCells(cells);
+ },
+
+ /**
+ * Function: copy
+ *
+ * Copies the given array of <mxCells> from the specified
+ * graph to <cells>.Returns the original array of cells that has
+ * been cloned.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> that contains the cells to be copied.
+ * cells - Optional array of <mxCells> to be copied.
+ */
+ copy: function(graph, cells)
+ {
+ cells = cells || graph.getSelectionCells();
+ var result = graph.getExportableCells(cells);
+ mxClipboard.insertCount = 1;
+ mxClipboard.cells = graph.cloneCells(result);
+
+ return result;
+ },
+
+ /**
+ * Function: paste
+ *
+ * Pastes the <cells> into the specified graph restoring
+ * the relation to <parents>, if possible. If the parents
+ * are no longer in the graph or invisible then the
+ * cells are added to the graph's default or into the
+ * swimlane under the cell's new location if one exists.
+ * The cells are added to the graph using <mxGraph.importCells>.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to paste the <cells> into.
+ */
+ paste: function(graph)
+ {
+ if (mxClipboard.cells != null)
+ {
+ var cells = graph.getImportableCells(mxClipboard.cells);
+ 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);
+ }
+ }
+
+};
diff --git a/src/js/util/mxConstants.js b/src/js/util/mxConstants.js
new file mode 100644
index 0000000..8d11dc1
--- /dev/null
+++ b/src/js/util/mxConstants.js
@@ -0,0 +1,1911 @@
+/**
+ * $Id: mxConstants.js,v 1.127 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+ 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: 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 colorr. Default is #0000FF.
+ */
+ DEFAULT_VALID_COLOR: '#00FF00',
+
+ /**
+ * Variable: DEFAULT_INVALID_COLOR
+ *
+ * Specifies the default invalid color. Default is #FF0000.
+ */
+ DEFAULT_INVALID_COLOR: '#FF0000',
+
+ /**
+ * Variable: HIGHLIGHT_STROKEWIDTH
+ *
+ * Defines the strokewidth to be used for the highlights.
+ * Default is 3.
+ */
+ HIGHLIGHT_STROKEWIDTH: 3,
+
+ /**
+ * 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_BEND_HANDLE
+ *
+ * Defines the cursor for a movable bend. Default is 'pointer'.
+ */
+ CURSOR_BEND_HANDLE: 'pointer',
+
+ /**
+ * 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 7.
+ */
+ HANDLE_SIZE: 7,
+
+ /**
+ * 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 in points. Default is
+ * Arial,Helvetica.
+ */
+ DEFAULT_FONTFAMILY: 'Arial,Helvetica',
+
+ /**
+ * Variable: DEFAULT_FONTSIZE
+ *
+ * Defines the default size for all fonts in points. Default is 11.
+ */
+ DEFAULT_FONTSIZE: 11,
+
+ /**
+ * Variable: DEFAULT_STARTSIZE
+ *
+ * Defines the default start size for swimlanes. Default is 40.
+ */
+ DEFAULT_STARTSIZE: 40,
+
+ /**
+ * Variable: DEFAULT_MARKERSIZE
+ *
+ * Defines the default size for all markers. Default is 6.
+ */
+ DEFAULT_MARKERSIZE: 6,
+
+ /**
+ * Variable: DEFAULT_IMAGESIZE
+ *
+ * Defines the default width and height for images used in the
+ * label shape. Default is 24.
+ */
+ DEFAULT_IMAGESIZE: 24,
+
+ /**
+ * Variable: ENTITY_SEGMENT
+ *
+ * Defines the length of the horizontal segment of an Entity Relation.
+ * This can be overridden using <mxConstants.STYLE_SEGMENT> style.
+ * Default is 30.
+ */
+ ENTITY_SEGMENT: 30,
+
+ /**
+ * Variable: RECTANGLE_ROUNDING_FACTOR
+ *
+ * Defines the rounding factor for rounded rectangles in percent between
+ * 0 and 1. Values should be smaller than 0.5. Default is 0.15.
+ */
+ RECTANGLE_ROUNDING_FACTOR: 0.15,
+
+ /**
+ * Variable: LINE_ARCSIZE
+ *
+ * Defines the size of the arcs for rounded edges. Default is 20.
+ */
+ LINE_ARCSIZE: 20,
+
+ /**
+ * Variable: ARROW_SPACING
+ *
+ * Defines the spacing between the arrow shape and its terminals. Default
+ * is 10.
+ */
+ ARROW_SPACING: 10,
+
+ /**
+ * 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, 826, 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, 826),
+
+ /**
+ * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+ *
+ * Defines the rectangle for the Letter portrait page format. The
+ * dimensions of this page format are 850x1100 pixels.
+ */
+ PAGE_FORMAT_LETTER_PORTRAIT: new mxRectangle(0, 0, 850, 1100),
+
+ /**
+ * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+ *
+ * Defines the rectangle for the Letter portrait page format. The dimensions
+ * of this page format are 850x1100 pixels.
+ */
+ PAGE_FORMAT_LETTER_LANDSCAPE: new mxRectangle(0, 0, 1100, 850),
+
+ /**
+ * Variable: NONE
+ *
+ * Defines the value for none. Default is "none".
+ */
+ NONE: 'none',
+
+ /**
+ * Variable: STYLE_PERIMETER
+ *
+ * Defines the key for the perimeter style. This is a function that defines
+ * the perimeter around a particular shape. Possible values are the
+ * functions defined in <mxPerimeter>. Alternatively, the constants in this
+ * class that start with <code>PERIMETER_</code> may be used to access
+ * perimeter styles in <mxStyleRegistry>.
+ */
+ 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.
+ */
+ 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.
+ */
+ STYLE_TARGET_PORT: 'targetPort',
+
+ /**
+ * Variable: STYLE_PORT_CONSTRAINT
+ *
+ * Defines the direction(s) that edges are allowed to connect to cells in.
+ * Possible values are <code>DIRECTION_NORTH, DIRECTION_SOUTH,
+ * DIRECTION_EAST</code> and <code>DIRECTION_WEST</code>.
+ */
+ STYLE_PORT_CONSTRAINT: 'portConstraint',
+
+ /**
+ * Variable: STYLE_OPACITY
+ *
+ * Defines the key for the opacity style. The type of the value is
+ * numeric and the possible range is 0-100.
+ */
+ STYLE_OPACITY: 'opacity',
+
+ /**
+ * 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.
+ */
+ STYLE_TEXT_OPACITY: 'textOpacity',
+
+ /**
+ * Variable: STYLE_OVERFLOW
+ *
+ * Defines the key for the overflow style. Possible values are 'visible',
+ * 'hidden' and 'fill'. 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 for the label. See
+ * <mxGraph.isLabelClipped>.
+ */
+ STYLE_OVERFLOW: 'overflow',
+
+ /**
+ * Variable: STYLE_ORTHOGONAL
+ *
+ * Defines if the connection points on either end of the edge should be
+ * computed so that the edge is vertical or horizontal if possible and
+ * if the point is not at a fixed location. Default is false. This is
+ * used in <mxGraph.isOrthogonal>, which also returns true if the edgeStyle
+ * of the edge is an elbow or entity.
+ */
+ STYLE_ORTHOGONAL: 'orthogonal',
+
+ /**
+ * Variable: STYLE_EXIT_X
+ *
+ * Defines the key for the horizontal relative coordinate connection point
+ * of an edge with its source terminal.
+ */
+ 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.
+ */
+ STYLE_EXIT_Y: 'exitY',
+
+ /**
+ * 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).
+ */
+ 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.
+ */
+ 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.
+ */
+ STYLE_ENTRY_Y: 'entryY',
+
+ /**
+ * 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).
+ */
+ STYLE_ENTRY_PERIMETER: 'entryPerimeter',
+
+ /**
+ * Variable: STYLE_WHITE_SPACE
+ *
+ * Defines the key for the white-space style. Possible values are 'nowrap'
+ * and 'wrap'. The default value is 'nowrap'. This value specifies how
+ * white-space inside a HTML vertex label should be handled. A value of
+ * 'nowrap' means the text will never wrap to the next line until a
+ * linefeed is encountered. A value of 'wrap' means text will wrap when
+ * necessary. This style is only used for HTML labels.
+ * See <mxGraph.isWrapping>.
+ */
+ 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.
+ */
+ 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.
+ */
+ STYLE_FILLCOLOR: 'fillColor',
+
+ /**
+ * 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.
+ */
+ STYLE_GRADIENTCOLOR: 'gradientColor',
+
+ /**
+ * Variable: STYLE_GRADIENT_DIRECTION
+ *
+ * Defines the key for the gradient direction. Possible values are
+ * <DIRECTION_EAST>, <DIRECTION_WEST>, <DIRECTION_NORTH> and
+ * <DIRECTION_SOUTH>. Default is <DIRECTION_SOUTH>. Generally, and by
+ * default in mxGraph, gradient painting is done from the value of
+ * <STYLE_FILLCOLOR> to the value of <STYLE_GRADIENTCOLOR>. Taking the
+ * example of <DIRECTION_NORTH>, this means <STYLE_FILLCOLOR> color at the
+ * bottom of paint pattern and <STYLE_GRADIENTCOLOR> at top, with a
+ * gradient in-between.
+ */
+ 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.
+ */
+ STYLE_STROKECOLOR: 'strokeColor',
+
+ /**
+ * Variable: STYLE_SEPARATORCOLOR
+ *
+ * Defines the key for the separatorColor style. Possible values are all
+ * HTML color names or HEX codes. This style is only used for
+ * <SHAPE_SWIMLANE> shapes.
+ */
+ 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.
+ */
+ STYLE_STROKEWIDTH: 'strokeWidth',
+
+ /**
+ * Variable: STYLE_ALIGN
+ *
+ * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
+ * <ALIGN_CENTER> and <ALIGN_RIGHT>. This value defines how the lines of
+ * the label are horizontally aligned. <ALIGN_LEFT> mean label text lines
+ * are aligned to left of the label bounds, <ALIGN_RIGHT> to the right of
+ * the label bounds and <ALIGN_CENTER> means the center of the text lines
+ * are aligned in the center of the label bounds. Note this value doesn't
+ * affect the positioning of the overall label bounds relative to the
+ * vertex, to move the label bounds horizontally, use
+ * <STYLE_LABEL_POSITION>.
+ */
+ STYLE_ALIGN: 'align',
+
+ /**
+ * Variable: STYLE_VERTICAL_ALIGN
+ *
+ * Defines the key for the verticalAlign style. Possible values are
+ * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. This value defines how
+ * the lines of the label are vertically aligned. <ALIGN_TOP> means the
+ * topmost label text line is aligned against the top of the label bounds,
+ * <ALIGN_BOTTOM> means the bottom-most label text line is aligned against
+ * the bottom of the label bounds and <ALIGN_MIDDLE> means there is equal
+ * spacing between the topmost text label line and the top of the label
+ * bounds and the bottom-most text label line and the bottom of the label
+ * bounds. Note this value doesn't affect the positioning of the overall
+ * label bounds relative to the vertex, to move the label bounds
+ * vertically, use <STYLE_VERTICAL_LABEL_POSITION>.
+ */
+ STYLE_VERTICAL_ALIGN: 'verticalAlign',
+
+ /**
+ * Variable: STYLE_LABEL_POSITION
+ *
+ * Defines the key for the horizontal label position of vertices. Possible
+ * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>. Default is
+ * <ALIGN_CENTER>. The label align defines the position of the label
+ * relative to the cell. <ALIGN_LEFT> means the entire label bounds is
+ * placed completely just to the left of the vertex, <ALIGN_RIGHT> means
+ * adjust to the right and <ALIGN_CENTER> means the label bounds are
+ * vertically aligned with the bounds of the vertex. Note this value
+ * doesn't affect the positioning of label within the label bounds, to move
+ * the label horizontally within the label bounds, use <STYLE_ALIGN>.
+ */
+ STYLE_LABEL_POSITION: 'labelPosition',
+
+ /**
+ * Variable: STYLE_VERTICAL_LABEL_POSITION
+ *
+ * Defines the key for the vertical label position of vertices. Possible
+ * values are <ALIGN_TOP>, <ALIGN_BOTTOM> and <ALIGN_MIDDLE>. Default is
+ * <ALIGN_MIDDLE>. The label align defines the position of the label
+ * relative to the cell. <ALIGN_TOP> means the entire label bounds is
+ * placed completely just on the top of the vertex, <ALIGN_BOTTOM> means
+ * adjust on the bottom and <ALIGN_MIDDLE> means the label bounds are
+ * horizontally aligned with the bounds of the vertex. Note this value
+ * doesn't affect the positioning of label within the label bounds, to move
+ * the label vertically within the label bounds, use
+ * <STYLE_VERTICAL_ALIGN>.
+ */
+ STYLE_VERTICAL_LABEL_POSITION: 'verticalLabelPosition',
+
+ /**
+ * Variable: STYLE_IMAGE_ASPECT
+ *
+ * Defines the key for the image aspect style. Possible values are 0 (do
+ * not preserve aspect) or 1 (keep aspect). This is only used in
+ * <mxImageShape>. Default is 1.
+ */
+ STYLE_IMAGE_ASPECT: 'imageAspect',
+
+ /**
+ * Variable: STYLE_IMAGE_ALIGN
+ *
+ * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
+ * <ALIGN_CENTER> and <ALIGN_RIGHT>. The value defines how any image in the
+ * vertex label is aligned horizontally within the label bounds of a
+ * <SHAPE_LABEL> shape.
+ */
+ STYLE_IMAGE_ALIGN: 'imageAlign',
+
+ /**
+ * Variable: STYLE_IMAGE_VERTICAL_ALIGN
+ *
+ * Defines the key for the verticalAlign style. Possible values are
+ * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. The value defines how
+ * any image in the vertex label is aligned vertically within the label
+ * bounds of a <SHAPE_LABEL> shape.
+ */
+ STYLE_IMAGE_VERTICAL_ALIGN: 'imageVerticalAlign',
+
+ /**
+ * Variable: STYLE_GLASS
+ *
+ * Defines the key for the glass style. Possible values are 0 (disabled) and
+ * 1(enabled). The default value is 0. This is used in <mxLabel>.
+ */
+ 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 to 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ STYLE_IMAGE_HEIGHT: 'imageHeight',
+
+ /**
+ * Variable: STYLE_IMAGE_BACKGROUND
+ *
+ * Defines the key for the image background color. This style is only used
+ * in <mxImageShape>. Possible values are all HTML color names or HEX
+ * codes.
+ */
+ STYLE_IMAGE_BACKGROUND: 'imageBackground',
+
+ /**
+ * Variable: STYLE_IMAGE_BORDER
+ *
+ * Defines the key for the image border color. This style is only used in
+ * <mxImageShape>. Possible values are all HTML color names or HEX codes.
+ */
+ STYLE_IMAGE_BORDER: 'imageBorder',
+
+ /**
+ * Variable: STYLE_IMAGE_FLIPH
+ *
+ * Defines the key for the horizontal image flip. This style is only used
+ * in <mxImageShape>. Possible values are 0 and 1. Default is 0.
+ */
+ STYLE_IMAGE_FLIPH: 'imageFlipH',
+
+ /**
+ * Variable: STYLE_IMAGE_FLIPV
+ *
+ * Defines the key for the vertical image flip. This style is only used
+ * in <mxImageShape>. Possible values are 0 and 1. Default is 0.
+ */
+ STYLE_IMAGE_FLIPV: 'imageFlipV',
+
+ /**
+ * Variable: STYLE_STENCIL_FLIPH
+ *
+ * Defines the key for the horizontal stencil flip. This style is only used
+ * for <mxStencilShape>. Possible values are 0 and 1. Default is 0.
+ */
+ STYLE_STENCIL_FLIPH: 'stencilFlipH',
+
+ /**
+ * Variable: STYLE_STENCIL_FLIPV
+ *
+ * Defines the key for the vertical stencil flip. This style is only used
+ * for <mxStencilShape>. Possible values are 0 and 1. Default is 0.
+ */
+ STYLE_STENCIL_FLIPV: 'stencilFlipV',
+
+ /**
+ * 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ STYLE_LABEL_PADDING: 'labelPadding',
+
+ /**
+ * Variable: STYLE_INDICATOR_SHAPE
+ *
+ * Defines the key for the indicator shape used within an <mxLabel>.
+ * Possible values are all SHAPE_* constants or the names of any new
+ * shapes. The indicatorShape has precedence over the indicatorImage.
+ */
+ STYLE_INDICATOR_SHAPE: 'indicatorShape',
+
+ /**
+ * Variable: STYLE_INDICATOR_IMAGE
+ *
+ * Defines the key for the indicator image used within an <mxLabel>.
+ * Possible values are all image URLs. The indicatorShape has
+ * precedence over the indicatorImage.
+ */
+ 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.
+ */
+ STYLE_INDICATOR_COLOR: 'indicatorColor',
+
+ /**
+ * Variable: STYLE_INDICATOR_STROKECOLOR
+ *
+ * Defines the key for the indicator stroke color in <mxLabel>.
+ * Possible values are all color codes.
+ */
+ STYLE_INDICATOR_STROKECOLOR: 'indicatorStrokeColor',
+
+ /**
+ * Variable: STYLE_INDICATOR_GRADIENTCOLOR
+ *
+ * Defines the key for the indicatorGradientColor style. Possible values
+ * are all HTML color names or HEX codes. This style is only supported in
+ * <SHAPE_LABEL> shapes.
+ */
+ STYLE_INDICATOR_GRADIENTCOLOR: 'indicatorGradientColor',
+
+ /**
+ * Variable: STYLE_INDICATOR_SPACING
+ *
+ * The defines the key for the spacing between the label and the
+ * indicator in <mxLabel>. Possible values are in pixels.
+ */
+ STYLE_INDICATOR_SPACING: 'indicatorSpacing',
+
+ /**
+ * Variable: STYLE_INDICATOR_WIDTH
+ *
+ * Defines the key for the indicator width.
+ * Possible values start at 0 (in pixels).
+ */
+ STYLE_INDICATOR_WIDTH: 'indicatorWidth',
+
+ /**
+ * Variable: STYLE_INDICATOR_HEIGHT
+ *
+ * Defines the key for the indicator height.
+ * Possible values start at 0 (in pixels).
+ */
+ STYLE_INDICATOR_HEIGHT: 'indicatorHeight',
+
+ /**
+ * Variable: STYLE_INDICATOR_DIRECTION
+ *
+ * Defines the key for the indicatorDirection style. The direction style is
+ * used to specify the direction of certain shapes (eg. <mxTriangle>).
+ * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
+ * <DIRECTION_NORTH> and <DIRECTION_SOUTH>.
+ */
+ STYLE_INDICATOR_DIRECTION: 'indicatorDirection',
+
+ /**
+ * Variable: STYLE_SHADOW
+ *
+ * Defines the key for the shadow style. The type of the value is Boolean.
+ */
+ 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.
+ */
+ STYLE_SEGMENT: 'segment',
+
+ /**
+ * Variable: STYLE_ENDARROW
+ *
+ * Defines the key for the end arrow marker.
+ * Possible values are all constants with an ARROW-prefix.
+ * This is only used in <mxConnector>.
+ *
+ * Example:
+ * (code)
+ * style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+ * (end)
+ */
+ STYLE_ENDARROW: 'endArrow',
+
+ /**
+ * Variable: STYLE_STARTARROW
+ *
+ * Defines the key for the start arrow marker.
+ * Possible values are all constants with an ARROW-prefix.
+ * This is only used in <mxConnector>.
+ * See <STYLE_ENDARROW>.
+ */
+ 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.
+ */
+ 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.
+ */
+ STYLE_STARTSIZE: 'startSize',
+
+ /**
+ * Variable: STYLE_ENDFILL
+ *
+ * Defines the key for the endFill style. Use 0 for no fill or 1
+ * (default) for fill. (This style is only exported via <mxImageExport>.)
+ */
+ STYLE_ENDFILL: 'endFill',
+
+ /**
+ * Variable: STYLE_STARTFILL
+ *
+ * Defines the key for the startFill style. Use 0 for no fill or 1
+ * (default) for fill. (This style is only exported via <mxImageExport>.)
+ */
+ STYLE_STARTFILL: 'startFill',
+
+ /**
+ * Variable: STYLE_DASHED
+ *
+ * Defines the key for the dashed style. Use 0 (default) for non-dashed or 1
+ * for 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 <mxConnector> shape.
+ */
+ STYLE_DASH_PATTERN: 'dashPattern',
+
+ /**
+ * 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.
+ */
+ STYLE_ROUNDED: 'rounded',
+
+ /**
+ * 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.
+ * (This style is only exported via <mxImageExport>.)
+ */
+ STYLE_ARCSIZE: 'arcSize',
+
+ /**
+ * Variable: STYLE_SMOOTH
+ *
+ * An experimental style for edges. This style is currently not available
+ * in the backends and is implemented differently for VML and SVG. The use
+ * of this style is currently only recommended for VML.
+ */
+ STYLE_SMOOTH: 'smooth',
+
+ /**
+ * 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.
+ */
+ 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.
+ */
+ 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).
+ */
+ 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).
+ */
+ 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).
+ */
+ 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).
+ */
+ 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).
+ */
+ 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).
+ */
+ STYLE_SPACING_RIGHT: 'spacingRight',
+
+ /**
+ * Variable: STYLE_HORIZONTAL
+ *
+ * Defines the key for the horizontal style. Possible values are
+ * true or false. This value only applies to vertices. If the <STYLE_SHAPE>
+ * is <code>SHAPE_SWIMLANE</code> 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.
+ */
+ STYLE_HORIZONTAL: 'horizontal',
+
+ /**
+ * Variable: STYLE_DIRECTION
+ *
+ * Defines the key for the direction style. The direction style is used
+ * to specify the direction of certain shapes (eg. <mxTriangle>).
+ * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
+ * <DIRECTION_NORTH> and <DIRECTION_SOUTH>.
+ */
+ STYLE_DIRECTION: 'direction',
+
+ /**
+ * Variable: STYLE_ELBOW
+ *
+ * Defines the key for the elbow style. Possible values are
+ * <ELBOW_HORIZONTAL> and <ELBOW_VERTICAL>. Default is <ELBOW_HORIZONTAL>.
+ * This defines how the three segment orthogonal edge style leaves its
+ * terminal vertices. The vertical style leaves the terminal vertices at
+ * the top and bottom sides.
+ */
+ STYLE_ELBOW: 'elbow',
+
+ /**
+ * Variable: STYLE_FONTCOLOR
+ *
+ * Defines the key for the fontColor style. Possible values are all HTML
+ * color names or HEX codes.
+ */
+ 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.
+ */
+ STYLE_FONTFAMILY: 'fontFamily',
+
+ /**
+ * Variable: STYLE_FONTSIZE
+ *
+ * Defines the key for the fontSize style (in points). The type of the value
+ * is int.
+ */
+ STYLE_FONTSIZE: 'fontSize',
+
+ /**
+ * Variable: STYLE_FONTSTYLE
+ *
+ * Defines the key for the fontStyle style. Values may be any logical AND
+ * (sum) of <FONT_BOLD>, <FONT_ITALIC>, <FONT_UNDERLINE> and <FONT_SHADOW>.
+ * The type of the value is int.
+ */
+ STYLE_FONTSTYLE: 'fontStyle',
+
+ /**
+ * Variable: STYLE_AUTOSIZE
+ *
+ * Defines the key for the autosize style. This specifies if a cell should be
+ * resized automatically if the value has changed. Possible values are 0 or 1.
+ * Default is 0. See <mxGraph.isAutoSizeCell>. This is normally combined with
+ * <STYLE_RESIZABLE> to disable manual sizing.
+ */
+ STYLE_AUTOSIZE: 'autosize',
+
+ /**
+ * Variable: STYLE_FOLDABLE
+ *
+ * Defines the key for the foldable style. This specifies if a cell is foldable
+ * using a folding icon. Possible values are 0 or 1. Default is 1. See
+ * <mxGraph.isCellFoldable>.
+ */
+ STYLE_FOLDABLE: 'foldable',
+
+ /**
+ * Variable: STYLE_EDITABLE
+ *
+ * Defines the key for the editable style. This specifies if the value of
+ * a cell can be edited using the in-place editor. Possible values are 0 or
+ * 1. Default is 1. See <mxGraph.isCellEditable>.
+ */
+ STYLE_EDITABLE: 'editable',
+
+ /**
+ * Variable: STYLE_BENDABLE
+ *
+ * Defines the key for the bendable style. This specifies if the control
+ * points of an edge can be moved. Possible values are 0 or 1. Default is
+ * 1. See <mxGraph.isCellBendable>.
+ */
+ STYLE_BENDABLE: 'bendable',
+
+ /**
+ * Variable: STYLE_MOVABLE
+ *
+ * Defines the key for the movable style. This specifies if a cell can
+ * be moved. Possible values are 0 or 1. Default is 1. See
+ * <mxGraph.isCellMovable>.
+ */
+ STYLE_MOVABLE: 'movable',
+
+ /**
+ * Variable: STYLE_RESIZABLE
+ *
+ * Defines the key for the resizable style. This specifies if a cell can
+ * be resized. Possible values are 0 or 1. Default is 1. See
+ * <mxGraph.isCellResizable>.
+ */
+ STYLE_RESIZABLE: 'resizable',
+
+ /**
+ * Variable: STYLE_CLONEABLE
+ *
+ * Defines the key for the cloneable style. This specifies if a cell can
+ * be cloned. Possible values are 0 or 1. Default is 1. See
+ * <mxGraph.isCellCloneable>.
+ */
+ STYLE_CLONEABLE: 'cloneable',
+
+ /**
+ * Variable: STYLE_DELETABLE
+ *
+ * Defines the key for the deletable style. This specifies if a cell can be
+ * deleted. Possible values are 0 or 1. Default is 1. See
+ * <mxGraph.isCellDeletable>.
+ */
+ 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.
+ */
+ STYLE_SHAPE: 'shape',
+
+ /**
+ * Variable: STYLE_EDGE
+ *
+ * Defines the key for the edge style. Possible values are the functions
+ * defined in <mxEdgeStyle>.
+ */
+ STYLE_EDGE: 'edgeStyle',
+
+ /**
+ * Variable: STYLE_LOOP
+ *
+ * Defines the key for the loop style. Possible values are the functions
+ * defined in <mxEdgeStyle>.
+ */
+ STYLE_LOOP: 'loopStyle',
+
+ /**
+ * 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.
+ */
+ 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.
+ */
+ 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_SHADOW
+ *
+ * Constant for fonts with a shadow. Default is 8.
+ */
+ FONT_SHADOW: 8,
+
+ /**
+ * Variable: SHAPE_RECTANGLE
+ *
+ * Name under which <mxRectangleShape> is registered
+ * in <mxCellRenderer>. Default is rectangle.
+ */
+ SHAPE_RECTANGLE: 'rectangle',
+
+ /**
+ * Variable: SHAPE_ELLIPSE
+ *
+ * Name under which <mxEllipse> is registered
+ * in <mxCellRenderer>. Default is ellipse.
+ */
+ SHAPE_ELLIPSE: 'ellipse',
+
+ /**
+ * Variable: SHAPE_DOUBLE_ELLIPSE
+ *
+ * Name under which <mxDoubleEllipse> is registered
+ * in <mxCellRenderer>. Default is doubleEllipse.
+ */
+ SHAPE_DOUBLE_ELLIPSE: 'doubleEllipse',
+
+ /**
+ * Variable: SHAPE_RHOMBUS
+ *
+ * Name under which <mxRhombus> is registered
+ * in <mxCellRenderer>. Default is rhombus.
+ */
+ SHAPE_RHOMBUS: 'rhombus',
+
+ /**
+ * Variable: SHAPE_LINE
+ *
+ * Name under which <mxLine> is registered
+ * in <mxCellRenderer>. Default is line.
+ */
+ SHAPE_LINE: 'line',
+
+ /**
+ * Variable: SHAPE_IMAGE
+ *
+ * Name under which <mxImageShape> is registered
+ * in <mxCellRenderer>. Default is image.
+ */
+ SHAPE_IMAGE: 'image',
+
+ /**
+ * Variable: SHAPE_ARROW
+ *
+ * Name under which <mxArrow> is registered
+ * in <mxCellRenderer>. Default is arrow.
+ */
+ SHAPE_ARROW: 'arrow',
+
+ /**
+ * Variable: SHAPE_LABEL
+ *
+ * Name under which <mxLabel> is registered
+ * in <mxCellRenderer>. Default is label.
+ */
+ SHAPE_LABEL: 'label',
+
+ /**
+ * Variable: SHAPE_CYLINDER
+ *
+ * Name under which <mxCylinder> is registered
+ * in <mxCellRenderer>. Default is cylinder.
+ */
+ SHAPE_CYLINDER: 'cylinder',
+
+ /**
+ * Variable: SHAPE_SWIMLANE
+ *
+ * Name under which <mxSwimlane> is registered
+ * in <mxCellRenderer>. Default is swimlane.
+ */
+ SHAPE_SWIMLANE: 'swimlane',
+
+ /**
+ * Variable: SHAPE_CONNECTOR
+ *
+ * Name under which <mxConnector> is registered
+ * in <mxCellRenderer>. Default is connector.
+ */
+ SHAPE_CONNECTOR: 'connector',
+
+ /**
+ * Variable: SHAPE_ACTOR
+ *
+ * Name under which <mxActor> is registered
+ * in <mxCellRenderer>. Default is actor.
+ */
+ SHAPE_ACTOR: 'actor',
+
+ /**
+ * Variable: SHAPE_CLOUD
+ *
+ * Name under which <mxCloud> is registered
+ * in <mxCellRenderer>. Default is cloud.
+ */
+ SHAPE_CLOUD: 'cloud',
+
+ /**
+ * Variable: SHAPE_TRIANGLE
+ *
+ * Name under which <mxTriangle> is registered
+ * in <mxCellRenderer>. Default is triangle.
+ */
+ SHAPE_TRIANGLE: 'triangle',
+
+ /**
+ * Variable: SHAPE_HEXAGON
+ *
+ * Name under which <mxHexagon> is registered
+ * in <mxCellRenderer>. Default is hexagon.
+ */
+ SHAPE_HEXAGON: 'hexagon',
+
+ /**
+ * Variable: ARROW_CLASSIC
+ *
+ * Constant for classic arrow markers.
+ */
+ ARROW_CLASSIC: 'classic',
+
+ /**
+ * Variable: ARROW_BLOCK
+ *
+ * Constant for block arrow markers.
+ */
+ ARROW_BLOCK: 'block',
+
+ /**
+ * Variable: ARROW_OPEN
+ *
+ * Constant for open arrow markers.
+ */
+ ARROW_OPEN: 'open',
+
+ /**
+ * 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
+ *
+ * Constant for 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: 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_TRIANGLE
+ *
+ * Name of the triangle perimeter. Can be used as a string value
+ * for the STYLE_PERIMETER style.
+ */
+ PERIMETER_TRIANGLE: 'trianglePerimeter'
+
+};
diff --git a/src/js/util/mxDictionary.js b/src/js/util/mxDictionary.js
new file mode 100644
index 0000000..a2e503a
--- /dev/null
+++ b/src/js/util/mxDictionary.js
@@ -0,0 +1,130 @@
+/**
+ * $Id: mxDictionary.js,v 1.12 2012-04-26 08:08:54 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDictionary
+ *
+ * A wrapper class for an associative array with object keys. Note: This
+ * implementation uses <mxObjectIdentitiy> to turn object keys into strings.
+ *
+ * Constructor: mxEventSource
+ *
+ * Constructs a new dictionary which allows object to be used as keys.
+ */
+function mxDictionary()
+{
+ this.clear();
+};
+
+/**
+ * Function: map
+ *
+ * Stores the (key, value) pairs in this dictionary.
+ */
+mxDictionary.prototype.map = null;
+
+/**
+ * Function: clear
+ *
+ * Clears the dictionary.
+ */
+mxDictionary.prototype.clear = function()
+{
+ this.map = {};
+};
+
+/**
+ * Function: get
+ *
+ * Returns the value for the given key.
+ */
+mxDictionary.prototype.get = function(key)
+{
+ var id = mxObjectIdentity.get(key);
+
+ return this.map[id];
+};
+
+/**
+ * Function: put
+ *
+ * Stores the value under the given key and returns the previous
+ * value for that key.
+ */
+mxDictionary.prototype.put = function(key, value)
+{
+ var id = mxObjectIdentity.get(key);
+ var previous = this.map[id];
+ this.map[id] = value;
+
+ return previous;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the value for the given key and returns the value that
+ * has been removed.
+ */
+mxDictionary.prototype.remove = function(key)
+{
+ var id = mxObjectIdentity.get(key);
+ var previous = this.map[id];
+ delete this.map[id];
+
+ return previous;
+};
+
+/**
+ * Function: getKeys
+ *
+ * Returns all keys as an array.
+ */
+mxDictionary.prototype.getKeys = function()
+{
+ var result = [];
+
+ for (var key in this.map)
+ {
+ result.push(key);
+ }
+
+ return result;
+};
+
+/**
+ * Function: getValues
+ *
+ * Returns all values as an array.
+ */
+mxDictionary.prototype.getValues = function()
+{
+ var result = [];
+
+ for (var key in this.map)
+ {
+ result.push(this.map[key]);
+ }
+
+ return result;
+};
+
+/**
+ * Function: visit
+ *
+ * Visits all entries in the dictionary using the given function with the
+ * following signature: function(key, value) where key is a string and
+ * value is an object.
+ *
+ * Parameters:
+ *
+ * visitor - A function that takes the key and value as arguments.
+ */
+mxDictionary.prototype.visit = function(visitor)
+{
+ for (var key in this.map)
+ {
+ visitor(key, this.map[key]);
+ }
+};
diff --git a/src/js/util/mxDivResizer.js b/src/js/util/mxDivResizer.js
new file mode 100644
index 0000000..2a2e4eb
--- /dev/null
+++ b/src/js/util/mxDivResizer.js
@@ -0,0 +1,151 @@
+/**
+ * $Id: mxDivResizer.js,v 1.22 2010-01-02 09:45:14 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * 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;
+};
diff --git a/src/js/util/mxDragSource.js b/src/js/util/mxDragSource.js
new file mode 100644
index 0000000..d0cafdf
--- /dev/null
+++ b/src/js/util/mxDragSource.js
@@ -0,0 +1,594 @@
+/**
+ * $Id: mxDragSource.js,v 1.14 2012-12-05 21:43:16 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * 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.
+ *
+ * TODO: 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
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ mxEvent.addListener(element, md, mxUtils.bind(this, this.mouseDown));
+};
+
+/**
+ * Variable: element
+ *
+ * Reference to the DOM node which was made draggable.
+ */
+mxDragSource.prototype.element = null;
+
+/**
+ * Variable: dropHandler
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dropHandler = null;
+
+/**
+ * Variable: dragOffset
+ *
+ * <mxPoint> that specifies the offset of the <dragElement>. Default is null.
+ */
+mxDragSource.prototype.dragOffset = null;
+
+/**
+ * Variable: dragElement
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dragElement = null;
+
+/**
+ * Variable: previewElement
+ *
+ * Optional <mxRectangle> that specifies the unscaled size of the preview.
+ */
+mxDragSource.prototype.previewElement = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if this drag source is enabled. Default is true.
+ */
+mxDragSource.prototype.enabled = true;
+
+/**
+ * Variable: currentGraph
+ *
+ * Reference to the <mxGraph> that is the current drop target.
+ */
+mxDragSource.prototype.currentGraph = null;
+
+/**
+ * Variable: currentDropTarget
+ *
+ * Holds the current drop target under the mouse.
+ */
+mxDragSource.prototype.currentDropTarget = null;
+
+/**
+ * Variable: currentPoint
+ *
+ * Holds the current drop location.
+ */
+mxDragSource.prototype.currentPoint = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
+ */
+mxDragSource.prototype.currentGuide = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
+ */
+mxDragSource.prototype.currentHighlight = null;
+
+/**
+ * Variable: autoscroll
+ *
+ * Specifies if the graph should scroll automatically. Default is true.
+ */
+mxDragSource.prototype.autoscroll = true;
+
+/**
+ * Variable: guidesEnabled
+ *
+ * Specifies if <mxGuide> should be enabled. Default is true.
+ */
+mxDragSource.prototype.guidesEnabled = true;
+
+/**
+ * Variable: gridEnabled
+ *
+ * Specifies if the grid should be allowed. Default is true.
+ */
+mxDragSource.prototype.gridEnabled = true;
+
+/**
+ * Variable: highlightDropTargets
+ *
+ * Specifies if drop targets should be highlighted. Default is true.
+ */
+mxDragSource.prototype.highlightDropTargets = true;
+
+/**
+ * Variable: dragElementZIndex
+ *
+ * ZIndex for the drag element. Default is 100.
+ */
+mxDragSource.prototype.dragElementZIndex = 100;
+
+/**
+ * Variable: dragElementOpacity
+ *
+ * Opacity of the drag element in %. Default is 70.
+ */
+mxDragSource.prototype.dragElementOpacity = 70;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns <enabled>.
+ */
+mxDragSource.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Sets <enabled>.
+ */
+mxDragSource.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isGuidesEnabled
+ *
+ * Returns <guidesEnabled>.
+ */
+mxDragSource.prototype.isGuidesEnabled = function()
+{
+ return this.guidesEnabled;
+};
+
+/**
+ * Function: setGuidesEnabled
+ *
+ * Sets <guidesEnabled>.
+ */
+mxDragSource.prototype.setGuidesEnabled = function(value)
+{
+ this.guidesEnabled = value;
+};
+
+/**
+ * Function: isGridEnabled
+ *
+ * Returns <gridEnabled>.
+ */
+mxDragSource.prototype.isGridEnabled = function()
+{
+ return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ *
+ * Sets <gridEnabled>.
+ */
+mxDragSource.prototype.setGridEnabled = function(value)
+{
+ this.gridEnabled = value;
+};
+
+/**
+ * Function: getGraphForEvent
+ *
+ * Returns the graph for the given mouse event. This implementation returns
+ * null.
+ */
+mxDragSource.prototype.getGraphForEvent = function(evt)
+{
+ return null;
+};
+
+/**
+ * Function: getDropTarget
+ *
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.getDropTarget = function(graph, x, y)
+{
+ return graph.getCellAt(x, y);
+};
+
+/**
+ * Function: createDragElement
+ *
+ * Creates and returns a clone of the <dragElementPrototype> or the <element>
+ * if the former is not defined.
+ */
+mxDragSource.prototype.createDragElement = function(evt)
+{
+ return this.element.cloneNode(true);
+};
+
+/**
+ * Function: createPreviewElement
+ *
+ * Creates and returns an element which can be used as a preview in the given
+ * graph.
+ */
+mxDragSource.prototype.createPreviewElement = function(graph)
+{
+ return null;
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.mouseDown = function(evt)
+{
+ if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null)
+ {
+ this.startDrag(evt);
+
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove);
+ mxEvent.addListener(document, mm, this.mouseMoveHandler);
+ this.mouseUpHandler = mxUtils.bind(this, this.mouseUp);
+ mxEvent.addListener(document, mu, this.mouseUpHandler);
+
+ // Prevents default action (native DnD for images in FF 10)
+ // but does not stop event propagation
+ mxEvent.consume(evt, true, false);
+ }
+};
+
+/**
+ * Function: startDrag
+ *
+ * Creates the <dragElement> using <createDragElement>.
+ */
+mxDragSource.prototype.startDrag = function(evt)
+{
+ this.dragElement = this.createDragElement(evt);
+ this.dragElement.style.position = 'absolute';
+ this.dragElement.style.zIndex = this.dragElementZIndex;
+ mxUtils.setOpacity(this.dragElement, this.dragElementOpacity);
+};
+
+
+/**
+ * Function: stopDrag
+ *
+ * Removes and destroys the <dragElement>.
+ */
+mxDragSource.prototype.stopDrag = function(evt)
+{
+ if (this.dragElement != null)
+ {
+ if (this.dragElement.parentNode != null)
+ {
+ this.dragElement.parentNode.removeChild(this.dragElement);
+ }
+
+ this.dragElement = null;
+ }
+};
+
+/**
+ * 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();
+
+ // Checks if event is inside the bounds of the graph container
+ return x >= offset.x - origin.x && y >= offset.y - origin.y &&
+ x <= offset.x - origin.x + graph.container.offsetWidth &&
+ y <= offset.y - origin.y + graph.container.offsetHeight;
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Gets the graph for the given event using <getGraphForEvent>, updates the
+ * <currentGraph>, calling <dragEnter> and <dragExit> on the new and old graph,
+ * respectively, and invokes <dragOver> if <currentGraph> is not null.
+ */
+mxDragSource.prototype.mouseMove = function(evt)
+{
+ var graph = this.getGraphForEvent(evt);
+
+ // Checks if event is inside the bounds of the graph container
+ if (graph != null && !this.graphContainsEvent(graph, evt))
+ {
+ graph = null;
+ }
+
+ if (graph != this.currentGraph)
+ {
+ if (this.currentGraph != null)
+ {
+ this.dragExit(this.currentGraph);
+ }
+
+ this.currentGraph = graph;
+
+ if (this.currentGraph != null)
+ {
+ this.dragEnter(this.currentGraph);
+ }
+ }
+
+ 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;
+ }
+
+ x += document.body.scrollLeft || document.documentElement.scrollLeft;
+ y += document.body.scrollTop || document.documentElement.scrollTop;
+ this.dragElement.style.left = x + 'px';
+ this.dragElement.style.top = y + 'px';
+ }
+ else if (this.dragElement != null)
+ {
+ this.dragElement.style.visibility = 'hidden';
+ }
+
+ mxEvent.consume(evt);
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Processes the mouse up event and invokes <drop>, <dragExit> and <stopDrag>
+ * as required.
+ */
+mxDragSource.prototype.mouseUp = function(evt)
+{
+ if (this.currentGraph != null)
+ {
+ if (this.currentPoint != null && (this.previewElement == null ||
+ this.previewElement.style.visibility != 'hidden'))
+ {
+ var scale = this.currentGraph.view.scale;
+ var tr = this.currentGraph.view.translate;
+ var x = this.currentPoint.x / scale - tr.x;
+ var y = this.currentPoint.y / scale - tr.y;
+
+ this.drop(this.currentGraph, evt, this.currentDropTarget, x, y);
+ }
+
+ this.dragExit(this.currentGraph);
+ }
+
+ this.stopDrag(evt);
+
+ this.currentGraph = null;
+
+ if (this.mouseMoveHandler != null)
+ {
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ mxEvent.removeListener(document, mm, this.mouseMoveHandler);
+ this.mouseMoveHandler = null;
+ }
+
+ if (this.mouseUpHandler != null)
+ {
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+ mxEvent.removeListener(document, mu, this.mouseUpHandler);
+ this.mouseUpHandler = null;
+ }
+
+ mxEvent.consume(evt);
+};
+
+/**
+ * Function: dragEnter
+ *
+ * Actives the given graph as a drop target.
+ */
+mxDragSource.prototype.dragEnter = function(graph)
+{
+ graph.isMouseDown = true;
+ this.previewElement = this.createPreviewElement(graph);
+
+ // 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);
+ }
+};
+
+/**
+ * Function: dragExit
+ *
+ * Deactivates the given graph as a drop target.
+ */
+mxDragSource.prototype.dragExit = function(graph)
+{
+ this.currentDropTarget = null;
+ this.currentPoint = null;
+ graph.isMouseDown = false;
+
+ if (this.previewElement != null)
+ {
+ if (this.previewElement.parentNode != null)
+ {
+ this.previewElement.parentNode.removeChild(this.previewElement);
+ }
+
+ this.previewElement = null;
+ }
+
+ if (this.currentGuide != null)
+ {
+ this.currentGuide.destroy();
+ this.currentGuide = null;
+ }
+
+ if (this.currentHighlight != null)
+ {
+ this.currentHighlight.destroy();
+ this.currentHighlight = null;
+ }
+};
+
+/**
+ * Function: dragOver
+ *
+ * Implements autoscroll, updates the <currentPoint>, highlights any drop
+ * targets and updates the preview.
+ */
+mxDragSource.prototype.dragOver = function(graph, evt)
+{
+ var offset = mxUtils.getOffset(graph.container);
+ var origin = mxUtils.getScrollOrigin(graph.container);
+ var x = mxEvent.getClientX(evt) - offset.x + origin.x;
+ var y = mxEvent.getClientY(evt) - offset.y + origin.y;
+
+ 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);
+ 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);
+ hideGuide = false;
+ x = delta.x;
+ y = delta.y;
+ }
+ else if (gridEnabled)
+ {
+ var scale = graph.view.scale;
+ var tr = graph.view.translate;
+ var off = graph.gridSize / 2;
+ x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;
+ y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;
+ }
+
+ if (this.currentGuide != null && hideGuide)
+ {
+ this.currentGuide.hide();
+ }
+
+ if (this.previewOffset != null)
+ {
+ x += this.previewOffset.x;
+ y += this.previewOffset.y;
+ }
+
+ this.previewElement.style.left = Math.round(x) + 'px';
+ this.previewElement.style.top = Math.round(y) + 'px';
+ this.previewElement.style.visibility = 'visible';
+ }
+
+ this.currentPoint = new mxPoint(x, y);
+};
+
+/**
+ * Function: drop
+ *
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.drop = function(graph, evt, dropTarget, x, y)
+{
+ this.dropHandler(graph, evt, dropTarget, x, y);
+
+ // 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.
+ graph.container.focus();
+};
diff --git a/src/js/util/mxEffects.js b/src/js/util/mxEffects.js
new file mode 100644
index 0000000..89d6a71
--- /dev/null
+++ b/src/js/util/mxEffects.js
@@ -0,0 +1,214 @@
+/**
+ * $Id: mxEffects.js,v 1.6 2012-01-04 10:01:16 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxEffects =
+{
+
+ /**
+ * Class: mxEffects
+ *
+ * Provides animation effects.
+ */
+
+ /**
+ * Function: animateChanges
+ *
+ * Asynchronous animated move operation. See also: <mxMorphing>.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ * var changes = evt.getProperty('edit').changes;
+ *
+ * if (changes.length < 10)
+ * {
+ * mxEffects.animateChanges(graph, changes);
+ * }
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> that received the changes.
+ * changes - Array of changes to be animated.
+ * done - Optional function argument that is invoked after the
+ * last step of the animation.
+ */
+ animateChanges: function(graph, changes, done)
+ {
+ var maxStep = 10;
+ var step = 0;
+
+ var animate = function()
+ {
+ var isRequired = false;
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ if (change instanceof mxGeometryChange ||
+ change instanceof mxTerminalChange ||
+ change instanceof mxValueChange ||
+ change instanceof mxChildChange ||
+ change instanceof mxStyleChange)
+ {
+ var state = graph.getView().getState(change.cell || change.child, false);
+
+ if (state != null)
+ {
+ isRequired = true;
+
+ if (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell))
+ {
+ mxUtils.setOpacity(state.shape.node, 100 * step / maxStep);
+ }
+ else
+ {
+ var scale = graph.getView().scale;
+
+ var dx = (change.geometry.x - change.previous.x) * scale;
+ var dy = (change.geometry.y - change.previous.y) * scale;
+
+ var sx = (change.geometry.width - change.previous.width) * scale;
+ var sy = (change.geometry.height - change.previous.height) * scale;
+
+ if (step == 0)
+ {
+ state.x -= dx;
+ state.y -= dy;
+ state.width -= sx;
+ state.height -= sy;
+ }
+ else
+ {
+ state.x += dx / maxStep;
+ state.y += dy / maxStep;
+ state.width += sx / maxStep;
+ state.height += sy / maxStep;
+ }
+
+ graph.cellRenderer.redraw(state);
+
+ // Fades all connected edges and children
+ mxEffects.cascadeOpacity(graph, change.cell, 100 * step / maxStep);
+ }
+ }
+ }
+ }
+
+ // Workaround to force a repaint in AppleWebKit
+ mxUtils.repaintGraph(graph, new mxPoint(1, 1));
+
+ if (step < maxStep && isRequired)
+ {
+ step++;
+ window.setTimeout(animate, delay);
+ }
+ else if (done != null)
+ {
+ done();
+ }
+ };
+
+ var delay = 30;
+ animate();
+ },
+
+ /**
+ * Function: cascadeOpacity
+ *
+ * Sets the opacity on the given cell and its descendants.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> that contains the cells.
+ * cell - <mxCell> to set the opacity for.
+ * opacity - New value for the opacity in %.
+ */
+ cascadeOpacity: function(graph, cell, opacity)
+ {
+ // Fades all children
+ var childCount = graph.model.getChildCount(cell);
+
+ for (var i=0; i<childCount; i++)
+ {
+ var child = graph.model.getChildAt(cell, i);
+ var childState = graph.getView().getState(child);
+
+ if (childState != null)
+ {
+ mxUtils.setOpacity(childState.shape.node, opacity);
+ mxEffects.cascadeOpacity(graph, child, opacity);
+ }
+ }
+
+ // Fades all connected edges
+ var edges = graph.model.getEdges(cell);
+
+ if (edges != null)
+ {
+ for (var i=0; i<edges.length; i++)
+ {
+ var edgeState = graph.getView().getState(edges[i]);
+
+ if (edgeState != null)
+ {
+ mxUtils.setOpacity(edgeState.shape.node, opacity);
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: fadeOut
+ *
+ * Asynchronous fade-out operation.
+ */
+ fadeOut: function(node, from, remove, step, delay, isEnabled)
+ {
+ step = step || 40;
+ delay = delay || 30;
+
+ var opacity = from || 100;
+
+ mxUtils.setOpacity(node, opacity);
+
+ if (isEnabled || isEnabled == null)
+ {
+ var f = function()
+ {
+ opacity = Math.max(opacity-step, 0);
+ mxUtils.setOpacity(node, opacity);
+
+ if (opacity > 0)
+ {
+ window.setTimeout(f, delay);
+ }
+ else
+ {
+ node.style.visibility = 'hidden';
+
+ if (remove && node.parentNode)
+ {
+ node.parentNode.removeChild(node);
+ }
+ }
+ };
+ window.setTimeout(f, delay);
+ }
+ else
+ {
+ node.style.visibility = 'hidden';
+
+ if (remove && node.parentNode)
+ {
+ node.parentNode.removeChild(node);
+ }
+ }
+ }
+
+};
diff --git a/src/js/util/mxEvent.js b/src/js/util/mxEvent.js
new file mode 100644
index 0000000..f831631
--- /dev/null
+++ b/src/js/util/mxEvent.js
@@ -0,0 +1,1175 @@
+/**
+ * $Id: mxEvent.js,v 1.76 2012-12-07 07:39:03 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxEvent =
+{
+
+ /**
+ * Class: mxEvent
+ *
+ * Cross-browser DOM event support. For internal event handling,
+ * <mxEventSource> and the graph event dispatch loop in <mxGraph> are used.
+ *
+ * Memory Leaks:
+ *
+ * Use this class for adding and removing listeners to/from DOM nodes. The
+ * <removeAllListeners> function is provided to remove all listeners that
+ * have been added using <addListener>. The function should be invoked when
+ * the last reference is removed in the JavaScript code, typically when the
+ * referenced DOM node is removed from the DOM, and helps to reduce memory
+ * leaks in IE6.
+ *
+ * Variable: objects
+ *
+ * Contains all objects where any listener was added using <addListener>.
+ * This is used to reduce memory leaks in IE, see <mxClient.dispose>.
+ */
+ objects: [],
+
+ /**
+ * Function: addListener
+ *
+ * Binds the function to the specified event on the given element. Use
+ * <mxUtils.bind> in order to bind the "this" keyword inside the function
+ * to a given execution scope.
+ */
+ addListener: function()
+ {
+ var updateListenerList = function(element, eventName, funct)
+ {
+ if (element.mxListenerList == null)
+ {
+ element.mxListenerList = [];
+ mxEvent.objects.push(element);
+ }
+
+ 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: redirectMouseEvents
+ *
+ * Redirects the mouse events from the given DOM node to the graph dispatch
+ * loop using the event and given state as event arguments. State can
+ * either be an instance of <mxCellState> or a function that returns an
+ * <mxCellState>. The down, move, up and dblClick arguments are optional
+ * functions that take the trigger event as arguments and replace the
+ * default behaviour.
+ */
+ redirectMouseEvents: function(node, graph, state, down, move, up, dblClick)
+ {
+ var getState = function(evt)
+ {
+ return (typeof(state) == 'function') ? state(evt) : state;
+ };
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(node, md, function (evt)
+ {
+ if (down != null)
+ {
+ down(evt);
+ }
+ else if (!mxEvent.isConsumed(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ });
+
+ mxEvent.addListener(node, mm, function (evt)
+ {
+ if (move != null)
+ {
+ move(evt);
+ }
+ else if (!mxEvent.isConsumed(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ });
+
+ mxEvent.addListener(node, mu, 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)
+ {
+ 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]);
+ }
+ }
+ }
+ },
+
+ /**
+ * 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.
+ */
+ addMouseWheelListener: function(funct)
+ {
+ 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;
+ }
+
+ var delta = 0;
+
+ if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC)
+ {
+ delta = -evt.detail/2;
+ }
+ else
+ {
+ delta = evt.wheelDelta/120;
+ }
+
+ // Handles the event using the given function
+ if (delta != 0)
+ {
+ funct(evt, delta > 0);
+ }
+ };
+
+ // Webkit has NS event API, but IE event name and details
+ if (mxClient.IS_NS)
+ {
+ var eventName = (mxClient.IS_SF || mxClient.IS_GC) ?
+ 'mousewheel' : 'DOMMouseScroll';
+ mxEvent.addListener(window, eventName, wheelHandler);
+ }
+ else
+ {
+ // TODO: Does not work with Safari and Chrome but it should be
+ // working as tested in etc/markup/wheel.html
+ mxEvent.addListener(document, 'mousewheel', wheelHandler);
+ }
+ }
+ },
+
+ /**
+ * Function: disableContextMenu
+ *
+ * Disables the context menu for the given element.
+ */
+ disableContextMenu: function()
+ {
+ if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+ {
+ return function(element)
+ {
+ mxEvent.addListener(element, 'contextmenu', function()
+ {
+ return false;
+ });
+ };
+ }
+ else
+ {
+ return function(element)
+ {
+ element.setAttribute('oncontextmenu', 'return false;');
+ };
+ }
+ }(),
+
+ /**
+ * Function: getSource
+ *
+ * Returns the event's target or srcElement depending on the browser.
+ */
+ getSource: function(evt)
+ {
+ return (evt.srcElement != null) ? evt.srcElement : evt.target;
+ },
+
+ /**
+ * Function: isConsumed
+ *
+ * Returns true if the event has been consumed using <consume>.
+ */
+ isConsumed: function(evt)
+ {
+ return evt.isConsumed != null && evt.isConsumed;
+ },
+
+ /**
+ * Function: isLeftMouseButton
+ *
+ * Returns true if the left mouse button is pressed for the given event.
+ * To check if a button is pressed during a mouseMove you should use the
+ * <mxGraph.isMouseDown> property.
+ */
+ isLeftMouseButton: function(evt)
+ {
+ return evt.button == ((mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9)) ? 1 : 0);
+ },
+
+ /**
+ * Function: isRightMouseButton
+ *
+ * Returns true if the right mouse button was pressed. Note that this
+ * button might not be available on some systems. For handling a popup
+ * trigger <isPopupTrigger> should be used.
+ */
+ isRightMouseButton: function(evt)
+ {
+ return evt.button == 2;
+ },
+
+ /**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the event is a popup trigger. This implementation
+ * returns true if the right mouse button or shift was pressed.
+ */
+ isPopupTrigger: function(evt)
+ {
+ return mxEvent.isRightMouseButton(evt) ||
+ (mxEvent.isShiftDown(evt) &&
+ !mxEvent.isControlDown(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
+ 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,
+
+ //
+ // 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: 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: SESSION
+ *
+ * Specifies the event name for session.
+ */
+ SESSION: 'session',
+
+ /**
+ * 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: BEGIN_UPDATE
+ *
+ * Specifies the event name for beginUpdate.
+ */
+ BEGIN_UPDATE: 'beginUpdate',
+
+ /**
+ * Variable: END_UPDATE
+ *
+ * Specifies the event name for endUpdate.
+ */
+ END_UPDATE: 'endUpdate',
+
+ /**
+ * 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: 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: CLICK
+ *
+ * Specifies the event name for click.
+ */
+ CLICK: 'click',
+
+ /**
+ * 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'
+
+};
diff --git a/src/js/util/mxEventObject.js b/src/js/util/mxEventObject.js
new file mode 100644
index 0000000..cb08a55
--- /dev/null
+++ b/src/js/util/mxEventObject.js
@@ -0,0 +1,111 @@
+/**
+ * $Id: mxEventObject.js,v 1.11 2011-09-09 10:29:05 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEventObject
+ *
+ * The mxEventObject is a wrapper for all properties of a single event.
+ * Additionally, it also offers functions to consume the event and check if it
+ * was consumed as follows:
+ *
+ * (code)
+ * evt.consume();
+ * INV: evt.isConsumed() == true
+ * (end)
+ *
+ * Constructor: mxEventObject
+ *
+ * Constructs a new event object with the specified name. An optional
+ * sequence of key, value pairs can be appended to define properties.
+ *
+ * Example:
+ *
+ * (code)
+ * new mxEventObject("eventName", key1, val1, .., keyN, valN)
+ * (end)
+ */
+function mxEventObject(name)
+{
+ this.name = name;
+ this.properties = [];
+
+ for (var i = 1; i < arguments.length; i += 2)
+ {
+ if (arguments[i + 1] != null)
+ {
+ this.properties[arguments[i]] = arguments[i + 1];
+ }
+ }
+};
+
+/**
+ * Variable: name
+ *
+ * Holds the name.
+ */
+mxEventObject.prototype.name = null;
+
+/**
+ * Variable: properties
+ *
+ * Holds the properties as an associative array.
+ */
+mxEventObject.prototype.properties = null;
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state. Default is false.
+ */
+mxEventObject.prototype.consumed = false;
+
+/**
+ * Function: getName
+ *
+ * Returns <name>.
+ */
+mxEventObject.prototype.getName = function()
+{
+ return this.name;
+};
+
+/**
+ * Function: getProperties
+ *
+ * Returns <properties>.
+ */
+mxEventObject.prototype.getProperties = function()
+{
+ return this.properties;
+};
+
+/**
+ * Function: getProperty
+ *
+ * Returns the property for the given key.
+ */
+mxEventObject.prototype.getProperty = function(key)
+{
+ return this.properties[key];
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns true if the event has been consumed.
+ */
+mxEventObject.prototype.isConsumed = function()
+{
+ return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Consumes the event.
+ */
+mxEventObject.prototype.consume = function()
+{
+ this.consumed = true;
+};
diff --git a/src/js/util/mxEventSource.js b/src/js/util/mxEventSource.js
new file mode 100644
index 0000000..595f560
--- /dev/null
+++ b/src/js/util/mxEventSource.js
@@ -0,0 +1,191 @@
+/**
+ * $Id: mxEventSource.js,v 1.25 2012-04-16 10:54:20 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEventSource
+ *
+ * Base class for objects that dispatch named events. To create a subclass that
+ * inherits from mxEventSource, the following code is used.
+ *
+ * (code)
+ * function MyClass() { };
+ *
+ * MyClass.prototype = new mxEventSource();
+ * MyClass.prototype.constructor = MyClass;
+ * (end)
+ *
+ * Known Subclasses:
+ *
+ * <mxGraphModel>, <mxGraph>, <mxGraphView>, <mxEditor>, <mxCellOverlay>,
+ * <mxToolbar>, <mxWindow>
+ *
+ * Constructor: mxEventSource
+ *
+ * Constructs a new event source.
+ */
+function mxEventSource(eventSource)
+{
+ this.setEventSource(eventSource);
+};
+
+/**
+ * Variable: eventListeners
+ *
+ * Holds the event names and associated listeners in an array. The array
+ * contains the event name followed by the respective listener for each
+ * registered listener.
+ */
+mxEventSource.prototype.eventListeners = null;
+
+/**
+ * Variable: eventsEnabled
+ *
+ * Specifies if events can be fired. Default is true.
+ */
+mxEventSource.prototype.eventsEnabled = true;
+
+/**
+ * Variable: eventSource
+ *
+ * Optional source for events. Default is null.
+ */
+mxEventSource.prototype.eventSource = null;
+
+/**
+ * Function: isEventsEnabled
+ *
+ * Returns <eventsEnabled>.
+ */
+mxEventSource.prototype.isEventsEnabled = function()
+{
+ return this.eventsEnabled;
+};
+
+/**
+ * Function: setEventsEnabled
+ *
+ * Sets <eventsEnabled>.
+ */
+mxEventSource.prototype.setEventsEnabled = function(value)
+{
+ this.eventsEnabled = value;
+};
+
+/**
+ * Function: getEventSource
+ *
+ * Returns <eventSource>.
+ */
+mxEventSource.prototype.getEventSource = function()
+{
+ return this.eventSource;
+};
+
+/**
+ * Function: setEventSource
+ *
+ * Sets <eventSource>.
+ */
+mxEventSource.prototype.setEventSource = function(value)
+{
+ this.eventSource = value;
+};
+
+/**
+ * Function: addListener
+ *
+ * Binds the specified function to the given event name. If no event name
+ * is given, then the listener is registered for all events.
+ *
+ * The parameters of the listener are the sender and an <mxEventObject>.
+ */
+mxEventSource.prototype.addListener = function(name, funct)
+{
+ if (this.eventListeners == null)
+ {
+ this.eventListeners = [];
+ }
+
+ this.eventListeners.push(name);
+ this.eventListeners.push(funct);
+};
+
+/**
+ * Function: removeListener
+ *
+ * Removes all occurrences of the given listener from <eventListeners>.
+ */
+mxEventSource.prototype.removeListener = function(funct)
+{
+ if (this.eventListeners != null)
+ {
+ var i = 0;
+
+ while (i < this.eventListeners.length)
+ {
+ if (this.eventListeners[i+1] == funct)
+ {
+ this.eventListeners.splice(i, 2);
+ }
+ else
+ {
+ i += 2;
+ }
+ }
+ }
+};
+
+/**
+ * Function: fireEvent
+ *
+ * Dispatches the given event to the listeners which are registered for
+ * the event. The sender argument is optional. The current execution scope
+ * ("this") is used for the listener invocation (see <mxUtils.bind>).
+ *
+ * Example:
+ *
+ * (code)
+ * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN))
+ * (end)
+ *
+ * Parameters:
+ *
+ * evt - <mxEventObject> that represents the event.
+ * sender - Optional sender to be passed to the listener. Default value is
+ * the return value of <getEventSource>.
+ */
+mxEventSource.prototype.fireEvent = function(evt, sender)
+{
+ if (this.eventListeners != null &&
+ this.isEventsEnabled())
+ {
+ if (evt == null)
+ {
+ evt = new mxEventObject();
+ }
+
+ if (sender == null)
+ {
+ sender = this.getEventSource();
+ }
+
+ if (sender == null)
+ {
+ sender = this;
+ }
+
+ var args = [sender, evt];
+
+ for (var i = 0; i < this.eventListeners.length; i += 2)
+ {
+ var listen = this.eventListeners[i];
+
+ if (listen == null ||
+ listen == evt.getName())
+ {
+ this.eventListeners[i+1].apply(this, args);
+ }
+ }
+ }
+};
diff --git a/src/js/util/mxForm.js b/src/js/util/mxForm.js
new file mode 100644
index 0000000..bcee299
--- /dev/null
+++ b/src/js/util/mxForm.js
@@ -0,0 +1,202 @@
+/**
+ * $Id: mxForm.js,v 1.16 2010-10-08 04:21:45 david Exp $
+ * Copyright (c) 2006-2010, Gaudenz Alder, David Benson
+ */
+/**
+ * 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 a textfield for the given name and value and returns the textfield.
+ */
+mxForm.prototype.addText = function(name, value)
+{
+ var input = document.createElement('input');
+
+ input.setAttribute('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;
+};
diff --git a/src/js/util/mxGuide.js b/src/js/util/mxGuide.js
new file mode 100644
index 0000000..ab5c26d
--- /dev/null
+++ b/src/js/util/mxGuide.js
@@ -0,0 +1,364 @@
+/**
+ * $Id: mxGuide.js,v 1.7 2012-04-13 12:53:30 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGuide
+ *
+ * Implements the alignment of selection cells to other cells in the graph.
+ *
+ * Constructor: mxGuide
+ *
+ * Constructs a new guide object.
+ */
+function mxGuide(graph, states)
+{
+ this.graph = graph;
+ this.setStates(states);
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph> instance.
+ */
+mxGuide.prototype.graph = null;
+
+/**
+ * Variable: states
+ *
+ * Contains the <mxCellStates> that are used for alignment.
+ */
+mxGuide.prototype.states = null;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies if horizontal guides are enabled. Default is true.
+ */
+mxGuide.prototype.horizontal = true;
+
+/**
+ * Variable: vertical
+ *
+ * Specifies if vertical guides are enabled. Default is true.
+ */
+mxGuide.prototype.vertical = true;
+
+/**
+ * Variable: vertical
+ *
+ * Holds the <mxShape> for the horizontal guide.
+ */
+mxGuide.prototype.guideX = null;
+
+/**
+ * Variable: vertical
+ *
+ * Holds the <mxShape> for the vertical guide.
+ */
+mxGuide.prototype.guideY = null;
+
+/**
+ * Variable: crisp
+ *
+ * Specifies if theguide should be rendered in crisp mode if applicable.
+ * Default is true.
+ */
+mxGuide.prototype.crisp = true;
+
+/**
+ * Function: setStates
+ *
+ * Sets the <mxCellStates> that should be used for alignment.
+ */
+mxGuide.prototype.setStates = function(states)
+{
+ this.states = states;
+};
+
+/**
+ * Function: isEnabledForEvent
+ *
+ * Returns true if the guide should be enabled for the given native event. This
+ * implementation always returns true.
+ */
+mxGuide.prototype.isEnabledForEvent = function(evt)
+{
+ return true;
+};
+
+/**
+ * Function: getGuideTolerance
+ *
+ * Returns the tolerance for the guides. Default value is
+ * gridSize * scale / 2.
+ */
+mxGuide.prototype.getGuideTolerance = function()
+{
+ return this.graph.gridSize * this.graph.view.scale / 2;
+};
+
+/**
+ * Function: createGuideShape
+ *
+ * Returns the mxShape to be used for painting the respective guide. This
+ * implementation returns a new, dashed and crisp <mxPolyline> using
+ * <mxConstants.GUIDE_COLOR> and <mxConstants.GUIDE_STROKEWIDTH> as the format.
+ *
+ * Parameters:
+ *
+ * horizontal - Boolean that specifies which guide should be created.
+ */
+mxGuide.prototype.createGuideShape = function(horizontal)
+{
+ var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);
+ guide.crisp = this.crisp;
+ guide.isDashed = true;
+
+ return guide;
+};
+
+/**
+ * Function: move
+ *
+ * Moves the <bounds> by the given <mxPoint> and returnt the snapped point.
+ */
+mxGuide.prototype.move = function(bounds, delta, gridEnabled)
+{
+ 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 dx = delta.x;
+ var dy = delta.y;
+
+ var overrideX = false;
+ var overrideY = false;
+
+ var tt = this.getGuideTolerance();
+ var ttX = tt;
+ var ttY = tt;
+
+ var b = bounds.clone();
+ b.x += delta.x;
+ b.y += delta.y;
+
+ 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)
+ {
+ x += this.graph.panDx;
+ var override = false;
+
+ if (Math.abs(x - center) < ttX)
+ {
+ dx = x - bounds.getCenterX();
+ ttX = Math.abs(x - center);
+ override = true;
+ }
+ else if (Math.abs(x - left) < ttX)
+ {
+ dx = x - bounds.x;
+ ttX = Math.abs(x - left);
+ override = true;
+ }
+ else if (Math.abs(x - right) < ttX)
+ {
+ dx = x - bounds.x - bounds.width;
+ ttX = Math.abs(x - right);
+ override = true;
+ }
+
+ if (override)
+ {
+ 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.init(this.graph.getView().getOverlayPane());
+
+ if (this.graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.guideX.node.setAttribute('pointer-events', 'none');
+ this.guideX.pipe.setAttribute('pointer-events', 'none');
+ }
+ }
+
+ var c = this.graph.container;
+ x -= this.graph.panDx;
+ this.guideX.points = [new mxPoint(x, -this.graph.panDy), new mxPoint(x, c.scrollHeight - 3 - this.graph.panDy)];
+ }
+
+ overrideX = overrideX || override;
+ };
+
+ // Snaps the top, middle or bottom to the given y-coordinate
+ function snapY(y)
+ {
+ y += this.graph.panDy;
+ var override = false;
+
+ if (Math.abs(y - middle) < ttY)
+ {
+ dy = y - bounds.getCenterY();
+ ttY = Math.abs(y - middle);
+ override = true;
+ }
+ else if (Math.abs(y - top) < ttY)
+ {
+ dy = y - bounds.y;
+ ttY = Math.abs(y - top);
+ override = true;
+ }
+ else if (Math.abs(y - bottom) < ttY)
+ {
+ dy = y - bounds.y - bounds.height;
+ ttY = Math.abs(y - bottom);
+ override = true;
+ }
+
+ if (override)
+ {
+ 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.init(this.graph.getView().getOverlayPane());
+
+ if (this.graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.guideY.node.setAttribute('pointer-events', 'none');
+ this.guideY.pipe.setAttribute('pointer-events', 'none');
+ }
+ }
+
+ var c = this.graph.container;
+ y -= this.graph.panDy;
+ this.guideY.points = [new mxPoint(-this.graph.panDx, y), new mxPoint(c.scrollWidth - 3 - this.graph.panDx, y)];
+ }
+
+ overrideY = overrideY || override;
+ };
+
+ for (var i = 0; i < this.states.length; i++)
+ {
+ var state = this.states[i];
+
+ if (state != null)
+ {
+ // Align x
+ if (this.horizontal)
+ {
+ snapX.call(this, state.getCenterX());
+ snapX.call(this, state.x);
+ snapX.call(this, state.x + state.width);
+ }
+
+ // Align y
+ if (this.vertical)
+ {
+ snapY.call(this, state.getCenterY());
+ snapY.call(this, state.y);
+ snapY.call(this, state.y + state.height);
+ }
+ }
+ }
+
+ if (!overrideX && this.guideX != null)
+ {
+ this.guideX.node.style.visibility = 'hidden';
+ }
+ else if (this.guideX != null)
+ {
+ 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)
+ {
+ this.guideY.node.style.visibility = 'visible';
+ this.guideY.redraw();
+ }
+
+ // Moves cells that are off-grid back to the grid on move
+ if (gridEnabled)
+ {
+ if (!overrideX)
+ {
+ var tx = bounds.x - (this.graph.snap(bounds.x /
+ scale - trx.x) + trx.x) * scale;
+ dx = this.graph.snap(dx / scale) * scale - tx;
+ }
+
+ if (!overrideY)
+ {
+ var ty = bounds.y - (this.graph.snap(bounds.y /
+ scale - trx.y) + trx.y) * scale;
+ dy = this.graph.snap(dy / scale) * scale - ty;
+ }
+ }
+
+ delta = new mxPoint(dx, dy);
+ }
+
+ return delta;
+};
+
+/**
+ * Function: hide
+ *
+ * Hides all current guides.
+ */
+mxGuide.prototype.hide = function()
+{
+ if (this.guideX != null)
+ {
+ this.guideX.node.style.visibility = 'hidden';
+ }
+
+ if (this.guideY != null)
+ {
+ this.guideY.node.style.visibility = '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;
+ }
+};
diff --git a/src/js/util/mxImage.js b/src/js/util/mxImage.js
new file mode 100644
index 0000000..39d1a09
--- /dev/null
+++ b/src/js/util/mxImage.js
@@ -0,0 +1,40 @@
+/**
+ * $Id: mxImage.js,v 1.7 2010-01-02 09:45:14 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * 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;
diff --git a/src/js/util/mxImageBundle.js b/src/js/util/mxImageBundle.js
new file mode 100644
index 0000000..dc4c2cf
--- /dev/null
+++ b/src/js/util/mxImageBundle.js
@@ -0,0 +1,98 @@
+/**
+ * $Id: mxImageBundle.js,v 1.3 2011-01-20 19:08:11 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * 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);
+ * graph.addImageBundle(bundle);
+ * (end);
+ *
+ * Alt is an optional boolean (default is false) that specifies if the value
+ * or the fallback should be returned in <getImage>.
+ *
+ * The image can then be referenced in any cell style using image=myImage.
+ * If you are using mxOutline, you should use the same image bundles in the
+ * graph that renders the outline.
+ *
+ * The keys for images are resolved in <mxGraph.postProcessCellStyle> and
+ * turned into a data URI if the returned value has a short data URI format
+ * as specified above.
+ *
+ * A typical value for the fallback is a MTHML link as defined in RFC 2557.
+ * Note that this format requires a file to be dynamically created on the
+ * server-side, or the page that contains the graph to be modified to contain
+ * the resources, this can be done by adding a comment that contains the
+ * resource in the HEAD section of the page after the title tag.
+ *
+ * This type of fallback mechanism should be used in IE6 and IE7. IE8 does
+ * support data URIs, but the maximum size is limited to 32 KB, which means
+ * all data URIs should be limited to 32 KB.
+ */
+function mxImageBundle(alt)
+{
+ this.images = [];
+ this.alt = (alt != null) ? alt : false;
+};
+
+/**
+ * Variable: images
+ *
+ * Maps from keys to images.
+ */
+mxImageBundle.prototype.images = null;
+
+/**
+ * Variable: alt
+ *
+ * Specifies if the fallback representation should be returned.
+ */
+mxImageBundle.prototype.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 <alt>. The fallback is returned if
+ * <alt> is true, the value is returned otherwise.
+ */
+mxImageBundle.prototype.getImage = function(key)
+{
+ var result = null;
+
+ if (key != null)
+ {
+ var img = this.images[key];
+
+ if (img != null)
+ {
+ result = (this.alt) ? img.fallback : img.value;
+ }
+ }
+
+ return result;
+};
diff --git a/src/js/util/mxImageExport.js b/src/js/util/mxImageExport.js
new file mode 100644
index 0000000..dcbcf9a
--- /dev/null
+++ b/src/js/util/mxImageExport.js
@@ -0,0 +1,1412 @@
+/**
+ * $Id: mxImageExport.js,v 1.47 2012-09-24 14:54:32 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxImageExport
+ *
+ * Creates a new image export instance to be used with an export canvas. Here
+ * is an example that uses this class to create an image via a backend using
+ * <mxXmlExportCanvas>.
+ *
+ * (code)
+ * var xmlDoc = mxUtils.createXmlDocument();
+ * var root = xmlDoc.createElement('output');
+ * xmlDoc.appendChild(root);
+ *
+ * var xmlCanvas = new mxXmlCanvas2D(root);
+ * var imgExport = new mxImageExport();
+ * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
+ *
+ * var bounds = graph.getGraphBounds();
+ * var w = Math.ceil(bounds.x + bounds.width);
+ * var h = Math.ceil(bounds.y + bounds.height);
+ *
+ * var xml = mxUtils.getXml(root);
+ * new mxXmlRequest('export', 'format=png&w=' + w +
+ * '&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))
+ * .simulate(document, '_blank');
+ * (end)
+ *
+ * In order to export images for a graph whose container is not visible or not
+ * part of the DOM, the following workaround can be used to compute the size of
+ * the labels.
+ *
+ * (code)
+ * mxText.prototype.getTableSize = function(table)
+ * {
+ * var oldParent = table.parentNode;
+ *
+ * document.body.appendChild(table);
+ * var size = new mxRectangle(0, 0, table.offsetWidth, table.offsetHeight);
+ * oldParent.appendChild(table);
+ *
+ * return size;
+ * };
+ * (end)
+ *
+ * Constructor: mxImageExport
+ *
+ * Constructs a new image export.
+ */
+function mxImageExport()
+{
+ this.initShapes();
+ this.initMarkers();
+};
+
+/**
+ * Variable: includeOverlays
+ *
+ * Specifies if overlays should be included in the export. Default is false.
+ */
+mxImageExport.prototype.includeOverlays = false;
+
+/**
+ * Variable: glassSize
+ *
+ * Reference to the thread while the animation is running.
+ */
+mxImageExport.prototype.glassSize = 0.4;
+
+/**
+ * Variable: shapes
+ *
+ * Holds implementations for the built-in shapes.
+ */
+mxImageExport.prototype.shapes = null;
+
+/**
+ * Variable: markers
+ *
+ * Holds implementations for the built-in markers.
+ */
+mxImageExport.prototype.markers = null;
+
+/**
+ * Function: drawState
+ *
+ * Draws the given state and all its descendants to the given canvas.
+ */
+mxImageExport.prototype.drawState = function(state, canvas)
+{
+ if (state != null)
+ {
+ if (state.shape != null)
+ {
+ var shape = (state.shape.stencil != null) ?
+ state.shape.stencil :
+ this.shapes[state.style[mxConstants.STYLE_SHAPE]];
+
+ if (shape == null)
+ {
+ // Checks if there is a custom shape
+ if (typeof(state.shape.redrawPath) == 'function')
+ {
+ shape = this.createShape(state, canvas);
+ }
+ // Uses a rectangle for all vertices where no shape can be found
+ else if (state.view.graph.getModel().isVertex(state.cell))
+ {
+ shape = this.shapes['rectangle'];
+ }
+ }
+
+ if (shape != null)
+ {
+ this.drawShape(state, canvas, shape);
+
+ if (this.includeOverlays)
+ {
+ this.drawOverlays(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.drawState(childState, canvas);
+ }
+ }
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates a shape wrapper for the custom shape in the given cell state and
+ * links its output to the given canvas.
+ */
+mxImageExport.prototype.createShape = function(state, canvas)
+{
+ return {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ var path =
+ {
+ translate: new mxPoint(bounds.x, bounds.y),
+ moveTo: function(x, y)
+ {
+ canvas.moveTo(this.translate.x + x, this.translate.y + y);
+ },
+ lineTo: function(x, y)
+ {
+ canvas.lineTo(this.translate.x + x, this.translate.y + y);
+ },
+ quadTo: function(x1, y1, x, y)
+ {
+ canvas.quadTo(this.translate.x + x1, this.translate.y + y1, this.translate.x + x, this.translate.y + y);
+ },
+ curveTo: function(x1, y1, x2, y2, x, y)
+ {
+ canvas.curveTo(this.translate.x + x1, this.translate.y + y1, this.translate.x + x2, this.translate.y + y2, this.translate.x + x, this.translate.y + y);
+ },
+ end: function()
+ {
+ // do nothing
+ },
+ close: function()
+ {
+ canvas.close();
+ }
+ };
+
+ if (!background)
+ {
+ canvas.fillAndStroke();
+ }
+
+ // LATER: Remove empty path if shape does not implement foreground, add shadow/clipping
+ canvas.begin();
+ state.shape.redrawPath.call(state.shape, path, bounds.x, bounds.y, bounds.width, bounds.height, !background);
+
+ if (!background)
+ {
+ canvas.fillAndStroke();
+ }
+
+ return true;
+ }
+ };
+};
+
+/**
+ * Function: drawOverlays
+ *
+ * Draws the overlays for the given state. This is called if <includeOverlays>
+ * is true.
+ */
+mxImageExport.prototype.drawOverlays = function(state, canvas)
+{
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ var bounds = shape.bounds;
+
+ if (bounds != null)
+ {
+ canvas.image(bounds.x, bounds.y, bounds.width, bounds.height, shape.image);
+ }
+ });
+ }
+};
+
+/**
+ * Function: drawShape
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawShape = function(state, canvas, shape)
+{
+ var rotation = mxUtils.getNumber(state.style, mxConstants.STYLE_ROTATION, 0);
+ var direction = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION, null);
+
+ // New styles for shape flipping the stencil
+ var flipH = state.style[mxConstants.STYLE_STENCIL_FLIPH];
+ var flipV = state.style[mxConstants.STYLE_STENCIL_FLIPV];
+
+ if (flipH ? !flipV : flipV)
+ {
+ rotation *= -1;
+ }
+
+ // Default direction is east (ignored if rotation exists)
+ if (direction != null)
+ {
+ if (direction == 'north')
+ {
+ rotation += 270;
+ }
+ else if (direction == 'west')
+ {
+ rotation += 180;
+ }
+ else if (direction == 'south')
+ {
+ rotation += 90;
+ }
+ }
+
+ if (flipH && flipV)
+ {
+ rotation += 180;
+ flipH = false;
+ flipV = false;
+ }
+
+ // Saves the global state for each cell
+ canvas.save();
+
+ // Adds rotation and horizontal/vertical flipping
+ // FIXME: Rotation and stencil flip only supported for stencil shapes
+ rotation = rotation % 360;
+
+ if (rotation != 0 || flipH || flipV)
+ {
+ canvas.rotate(rotation, flipH, flipV, state.getCenterX(), state.getCenterY());
+ }
+
+ // Note: Overwritten in mxStencil.paintShape (can depend on aspect)
+ var scale = state.view.scale;
+ var sw = mxUtils.getNumber(state.style, mxConstants.STYLE_STROKEWIDTH, 1) * scale;
+ canvas.setStrokeWidth(sw);
+
+ var sw2 = sw / 2;
+ var bg = this.getBackgroundBounds(state);
+
+ // Stencils will rotate the bounds as required
+ if (state.shape.stencil == null && (direction == 'south' || direction == 'north'))
+ {
+ var dx = (bg.width - bg.height) / 2;
+ bg.x += dx;
+ bg.y += -dx;
+ var tmp = bg.width;
+ bg.width = bg.height;
+ bg.height = tmp;
+ }
+
+ var bb = new mxRectangle(bg.x - sw2, bg.y - sw2, bg.width + sw, bg.height + sw);
+ var alpha = mxUtils.getValue(state.style, mxConstants.STYLE_OPACITY, 100) / 100;
+
+ var shp = state.style[mxConstants.STYLE_SHAPE];
+ var imageShape = shp == mxConstants.SHAPE_IMAGE;
+ var gradientColor = (imageShape) ? null : mxUtils.getValue(state.style, mxConstants.STYLE_GRADIENTCOLOR);
+
+ // Converts colors with special keyword none to null
+ if (gradientColor == mxConstants.NONE)
+ {
+ gradientColor = null;
+ }
+
+ var fcKey = (imageShape) ? mxConstants.STYLE_IMAGE_BACKGROUND : mxConstants.STYLE_FILLCOLOR;
+ var fillColor = mxUtils.getValue(state.style, fcKey, null);
+
+ if (fillColor == mxConstants.NONE)
+ {
+ fillColor = null;
+ }
+
+ var scKey = (imageShape) ? mxConstants.STYLE_IMAGE_BORDER : mxConstants.STYLE_STROKECOLOR;
+ var strokeColor = mxUtils.getValue(state.style, scKey, null);
+
+ if (strokeColor == mxConstants.NONE)
+ {
+ strokeColor = null;
+ }
+
+ var glass = (fillColor != null && (shp == mxConstants.SHAPE_LABEL || shp == mxConstants.SHAPE_RECTANGLE));
+
+ // Draws the shadow if the fillColor is not transparent
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_SHADOW, false))
+ {
+ this.drawShadow(canvas, state, shape, rotation, flipH, flipV, bg, alpha, fillColor != null);
+ }
+
+ canvas.setAlpha(alpha);
+
+ // Sets the dashed state
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_DASHED, '0') == '1')
+ {
+ canvas.setDashed(true);
+
+ // Supports custom dash patterns
+ var dash = state.style['dashPattern'];
+
+ if (dash != null)
+ {
+ canvas.setDashPattern(dash);
+ }
+ }
+
+ // Draws background and foreground
+ if (strokeColor != null || fillColor != null)
+ {
+ if (strokeColor != null)
+ {
+ canvas.setStrokeColor(strokeColor);
+ }
+
+ if (fillColor != null)
+ {
+ if (gradientColor != null && gradientColor != 'transparent')
+ {
+ canvas.setGradient(fillColor, gradientColor, bg.x, bg.y, bg.width, bg.height, direction);
+ }
+ else
+ {
+ canvas.setFillColor(fillColor);
+ }
+ }
+
+ // Draws background and foreground of shape
+ glass = shape.drawShape(canvas, state, bg, true, false) && glass;
+ shape.drawShape(canvas, state, bg, false, false);
+ }
+
+ // Draws the glass effect
+ // Requires background in generic shape for clipping
+ if (glass && mxUtils.getValue(state.style, mxConstants.STYLE_GLASS, 0) == 1)
+ {
+ this.drawGlass(state, canvas, bb, shape, this.glassSize);
+ }
+
+ // Draws the image (currently disabled for everything but image and label shapes)
+ if (imageShape || shp == mxConstants.SHAPE_LABEL)
+ {
+ var src = state.view.graph.getImage(state);
+
+ if (src != null)
+ {
+ var imgBounds = this.getImageBounds(state);
+
+ if (imgBounds != null)
+ {
+ this.drawImage(state, canvas, imgBounds, src);
+ }
+ }
+ }
+
+ // Restores canvas state
+ canvas.restore();
+
+ // Draws the label (label has separate rotation)
+ var txt = state.text;
+
+ // Does not use mxCellRenderer.getLabelValue to avoid conversion of HTML entities for VML
+ var label = state.view.graph.getLabel(state.cell);
+
+ if (txt != null && label != null && label.length > 0)
+ {
+ canvas.save();
+ canvas.setAlpha(mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_OPACITY, 100) / 100);
+ var bounds = new mxRectangle(txt.boundingBox.x, txt.boundingBox.y, txt.boundingBox.width, txt.boundingBox.height);
+ var vert = mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 0;
+
+ // Vertical error offset
+ bounds.y += 2;
+
+ if (vert)
+ {
+ if (txt.dialect != mxConstants.DIALECT_SVG)
+ {
+ var cx = bounds.x + bounds.width / 2;
+ var cy = bounds.y + bounds.height / 2;
+ var tmp = bounds.width;
+ bounds.width = bounds.height;
+ bounds.height = tmp;
+ bounds.x = cx - bounds.width / 2;
+ bounds.y = cy - bounds.height / 2;
+ }
+ else if (txt.dialect == mxConstants.DIALECT_SVG)
+ {
+ // Workarounds for different label bounding boxes (mostly ignoring rotation).
+ // LATER: Fix in mxText so that the bounding box is consistent and rotated.
+ // TODO: Check non-center/middle-aligned vertical labels in VML for IE8.
+ var b = state.y + state.height;
+ var cx = bounds.getCenterX() - state.x;
+ var cy = bounds.getCenterY() - state.y;
+
+ var y = b - cx - bounds.height / 2;
+ bounds.x = state.x + cy - bounds.width / 2;
+ bounds.y = y;
+ //bounds.x -= state.height / 2 - state.width / 2;
+ //bounds.y -= state.width / 2 - state.height / 2;
+ }
+ }
+
+ this.drawLabelBackground(state, canvas, bounds, vert);
+ this.drawLabel(state, canvas, bounds, vert, label);
+ canvas.restore();
+ }
+};
+
+/**
+ * Function: drawGlass
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawShadow = function(canvas, state, shape, rotation, flipH, flipV, bounds, alpha, filled)
+{
+ // Requires background in generic shape for shadow, looks like only one
+ // fillAndStroke is allowed per current path, try working around that
+ // Computes rotated shadow offset
+ var rad = rotation * Math.PI / 180;
+ var cos = Math.cos(-rad);
+ var sin = Math.sin(-rad);
+ var offset = mxUtils.getRotatedPoint(new mxPoint(mxConstants.SHADOW_OFFSET_X, mxConstants.SHADOW_OFFSET_Y), cos, sin);
+
+ if (flipH)
+ {
+ offset.x *= -1;
+ }
+
+ if (flipV)
+ {
+ offset.y *= -1;
+ }
+
+ // TODO: Use save/restore instead of negative offset to restore (requires fix for HTML canvas)
+ canvas.translate(offset.x, offset.y);
+
+ // Returns true if a shadow has been painted (path has been created)
+ if (shape.drawShape(canvas, state, bounds, true, true))
+ {
+ canvas.setAlpha(mxConstants.SHADOW_OPACITY * alpha);
+ canvas.shadow(mxConstants.SHADOWCOLOR, filled);
+ }
+
+ canvas.translate(-offset.x, -offset.y);
+};
+
+/**
+ * Function: drawGlass
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawGlass = function(state, canvas, bounds, shape, size)
+{
+ // LATER: Clipping region should include stroke
+ if (shape.drawShape(canvas, state, bounds, true, false))
+ {
+ canvas.save();
+ canvas.clip();
+ canvas.setGlassGradient(bounds.x, bounds.y, bounds.width, bounds.height);
+
+ canvas.begin();
+ canvas.moveTo(bounds.x, bounds.y);
+ canvas.lineTo(bounds.x, (bounds.y + bounds.height * size));
+ canvas.quadTo((bounds.x + bounds.width * 0.5),
+ (bounds.y + bounds.height * 0.7), bounds.x + bounds.width,
+ (bounds.y + bounds.height * size));
+ canvas.lineTo(bounds.x + bounds.width, bounds.y);
+ canvas.close();
+
+ canvas.fill();
+ canvas.restore();
+ }
+};
+
+/**
+ * Function: drawImage
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawImage = function(state, canvas, bounds, image)
+{
+ var aspect = mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1;
+ var flipH = mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE_FLIPH, 0) == 1;
+ var flipV = mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE_FLIPV, 0) == 1;
+
+ canvas.image(bounds.x, bounds.y, bounds.width, bounds.height, image, aspect, flipH, flipV);
+};
+
+/**
+ * Function: drawLabelBackground
+ *
+ * Draws background for the label of the given state to the given canvas.
+ */
+mxImageExport.prototype.drawLabelBackground = function(state, canvas, bounds, vert)
+{
+ var stroke = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_BORDERCOLOR);
+ var fill = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR);
+
+ if (stroke == mxConstants.NONE)
+ {
+ stroke = null;
+ }
+
+ if (fill == mxConstants.NONE)
+ {
+ fill = null;
+ }
+
+ if (stroke != null || fill != null)
+ {
+ var x = bounds.x;
+ var y = bounds.y - mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_PADDING, 0);
+ var w = bounds.width;
+ var h = bounds.height;
+
+ if (vert)
+ {
+ x += (w - h) / 2;
+ y += (h - w) / 2;
+ var tmp = w;
+ w = h;
+ h = tmp;
+ }
+
+ if (fill != null)
+ {
+ canvas.setFillColor(fill);
+ }
+
+ if (stroke != null)
+ {
+ canvas.setStrokeColor(stroke);
+ canvas.setStrokeWidth(1);
+ canvas.setDashed(false);
+ }
+
+ canvas.rect(x, y, w, h);
+
+ if (fill != null && stroke != null)
+ {
+ canvas.fillAndStroke();
+ }
+ else if (fill != null)
+ {
+ canvas.fill();
+ }
+ else if (stroke != null)
+ {
+ canvas.stroke();
+ }
+ }
+};
+
+/**
+ * Function: drawLabel
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawLabel = function(state, canvas, bounds, vert, str)
+{
+ var scale = state.view.scale;
+
+ // Applies color
+ canvas.setFontColor(mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, '#000000'));
+
+ // Applies font settings
+ canvas.setFontFamily(mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY,
+ mxConstants.DEFAULT_FONTFAMILY));
+ canvas.setFontStyle(mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0));
+ canvas.setFontSize(mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE,
+ mxConstants.DEFAULT_FONTSIZE) * scale);
+
+ var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
+
+ // Uses null alignment for default values (valign default is 'top' which is fine)
+ if (align == 'left')
+ {
+ align = null;
+ }
+
+ var y = bounds.y - mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_PADDING, 0);
+ var wrap = state.view.graph.isWrapping(state.cell);
+ var html = state.view.graph.isHtmlLabel(state.cell);
+
+ // Replaces linefeeds in HTML markup to match the display output
+ if (html && mxText.prototype.replaceLinefeeds)
+ {
+ str = str.replace(/\n/g, '<br/>');
+ }
+
+ canvas.text(bounds.x, y, bounds.width, bounds.height, str, align, null, vert, wrap, (html) ? 'html' : '');
+};
+
+/**
+ * Function: getBackgroundBounds
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.getBackgroundBounds = function(state)
+{
+ if (state.style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_SWIMLANE)
+ {
+ var scale = state.view.scale;
+ var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE) * scale;
+ var w = state.width;
+ var h = state.height;
+
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ h = start;
+ }
+ else
+ {
+ w = start;
+ }
+
+ return new mxRectangle(state.x, state.y, Math.min(state.width, w), Math.min(state.height, h));
+ }
+ else
+ {
+ return new mxRectangle(state.x, state.y, state.width, state.height);
+ }
+};
+
+/**
+ * Function: getImageBounds
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.getImageBounds = function(state)
+{
+ var bounds = new mxRectangle(state.x, state.y, state.width, state.height);
+ var style = state.style;
+
+ if (mxUtils.getValue(style, mxConstants.STYLE_SHAPE) != mxConstants.SHAPE_IMAGE)
+ {
+ var imgAlign = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);
+ var imgValign = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
+ var imgWidth = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_WIDTH, mxConstants.DEFAULT_IMAGESIZE);
+ var imgHeight = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_HEIGHT, mxConstants.DEFAULT_IMAGESIZE);
+ var spacing = mxUtils.getValue(style, mxConstants.STYLE_SPACING, 2);
+
+ if (imgAlign == mxConstants.ALIGN_CENTER)
+ {
+ bounds.x += (bounds.width - imgWidth) / 2;
+ }
+ else if (imgAlign == mxConstants.ALIGN_RIGHT)
+ {
+ bounds.x += bounds.width - imgWidth - spacing - 2;
+ }
+ else
+ // LEFT
+ {
+ bounds.x += spacing + 4;
+ }
+
+ if (imgValign == mxConstants.ALIGN_TOP)
+ {
+ bounds.y += spacing;
+ }
+ else if (imgValign == mxConstants.ALIGN_BOTTOM)
+ {
+ bounds.y += bounds.height - imgHeight - spacing;
+ }
+ else
+ // MIDDLE
+ {
+ bounds.y += (bounds.height - imgHeight) / 2;
+ }
+
+ bounds.width = imgWidth;
+ bounds.height = imgHeight;
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: drawMarker
+ *
+ * Initializes the built-in shapes.
+ */
+mxImageExport.prototype.drawMarker = function(canvas, state, source)
+{
+ var offset = null;
+
+ // Computes the norm and the inverse norm
+ var pts = state.absolutePoints;
+ var n = pts.length;
+
+ var p0 = (source) ? pts[1] : pts[n - 2];
+ var pe = (source) ? pts[0] : pts[n - 1];
+
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+
+ var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+
+ var unitX = dx / dist;
+ var unitY = dy / dist;
+
+ var size = mxUtils.getValue(state.style, (source) ?
+ mxConstants.STYLE_STARTSIZE :
+ mxConstants.STYLE_ENDSIZE,
+ mxConstants.DEFAULT_MARKERSIZE);
+
+ // Allow for stroke width in the end point used and the
+ // orthogonal vectors describing the direction of the marker
+ // TODO: Should get strokewidth from canvas (same for strokecolor)
+ var sw = mxUtils.getValue(state.style, mxConstants.STYLE_STROKEWIDTH, 1);
+
+ pe = pe.clone();
+
+ var type = mxUtils.getValue(state.style, (source) ?
+ mxConstants.STYLE_STARTARROW :
+ mxConstants.STYLE_ENDARROW);
+ var f = this.markers[type];
+
+ if (f != null)
+ {
+ offset = f(canvas, state, type, pe, unitX, unitY, size, source, sw);
+ }
+
+ return offset;
+};
+
+/**
+ * Function: initShapes
+ *
+ * Initializes the built-in shapes.
+ */
+mxImageExport.prototype.initShapes = function()
+{
+ this.shapes = [];
+
+ // Implements the rectangle and rounded rectangle shape
+ this.shapes['rectangle'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ // Paints the shape
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false))
+ {
+ var f = mxUtils.getValue(state.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+ var r = Math.min(bounds.width * f, bounds.height * f);
+ canvas.roundrect(bounds.x, bounds.y, bounds.width, bounds.height, r, r);
+ }
+ else
+ {
+ canvas.rect(bounds.x, bounds.y, bounds.width, bounds.height);
+ }
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ // Implements the swimlane shape
+ this.shapes['swimlane'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false))
+ {
+ var r = Math.min(bounds.width * mxConstants.RECTANGLE_ROUNDING_FACTOR,
+ bounds.height * mxConstants.RECTANGLE_ROUNDING_FACTOR);
+ canvas.roundrect(bounds.x, bounds.y, bounds.width, bounds.height, r, r);
+ }
+ else
+ {
+ canvas.rect(bounds.x, bounds.y, bounds.width, bounds.height);
+ }
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ canvas.begin();
+
+ var x = state.x;
+ var y = state.y;
+ var w = state.width;
+ var h = state.height;
+
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 0)
+ {
+ x += bounds.width;
+ w -= bounds.width;
+
+ canvas.moveTo(x, y);
+ canvas.lineTo(x + w, y);
+ canvas.lineTo(x + w, y + h);
+ canvas.lineTo(x, y + h);
+ }
+ else
+ {
+ y += bounds.height;
+ h -= bounds.height;
+
+ canvas.moveTo(x, y);
+ canvas.lineTo(x, y + h);
+ canvas.lineTo(x + w, y + h);
+ canvas.lineTo(x + w, y);
+ }
+
+ canvas.stroke();
+ }
+ }
+ };
+
+ this.shapes['image'] = this.shapes['rectangle'];
+ this.shapes['label'] = this.shapes['rectangle'];
+
+ var imageExport = this;
+
+ this.shapes['connector'] =
+ {
+ translatePoint: function(points, index, offset)
+ {
+ if (offset != null)
+ {
+ var pt = points[index].clone();
+ pt.x += offset.x;
+ pt.y += offset.y;
+ points[index] = pt;
+ }
+ },
+
+ drawShape: function(canvas, state, bounds, background, shadow)
+ {
+ if (background)
+ {
+ var rounded = mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false);
+ var arcSize = mxConstants.LINE_ARCSIZE / 2;
+
+ // Does not draw the markers in the shadow to match the display
+ canvas.setFillColor((shadow) ? mxConstants.NONE : mxUtils.getValue(state.style, mxConstants.STYLE_STROKECOLOR, "#000000"));
+ canvas.setDashed(false);
+ var pts = state.absolutePoints.slice();
+ this.translatePoint(pts, 0, imageExport.drawMarker(canvas, state, true));
+ this.translatePoint(pts, pts.length - 1, imageExport.drawMarker(canvas, state, false));
+ canvas.setDashed(mxUtils.getValue(state.style, mxConstants.STYLE_DASHED, '0') == '1');
+
+ var pt = pts[0];
+ var pe = pts[pts.length - 1];
+ canvas.begin();
+ canvas.moveTo(pt.x, pt.y);
+
+ // Draws the line segments
+ for (var i = 1; i < pts.length - 1; i++)
+ {
+ var tmp = pts[i];
+ var dx = pt.x - tmp.x;
+ var dy = pt.y - tmp.y;
+
+ if ((rounded && i < pts.length - 1) && (dx != 0 || dy != 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;
+ canvas.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[i + 1];
+ 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;
+
+ canvas.curveTo(tmp.x, tmp.y, tmp.x, tmp.y, x2, y2);
+ tmp = new mxPoint(x2, y2);
+ }
+ else
+ {
+ canvas.lineTo(tmp.x, tmp.y);
+ }
+
+ pt = tmp;
+ }
+
+ canvas.lineTo(pe.x, pe.y);
+ canvas.stroke();
+
+ return true;
+ }
+ else
+ {
+ // no foreground
+ }
+ }
+ };
+
+ this.shapes['arrow'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ // Geometry of arrow
+ var spacing = mxConstants.ARROW_SPACING;
+ var width = mxConstants.ARROW_WIDTH;
+ var arrow = mxConstants.ARROW_SIZE;
+
+ // Base vector (between end points)
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length - 1];
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ var length = dist - 2 * spacing - arrow;
+
+ // Computes the norm and the inverse norm
+ var nx = dx / dist;
+ var ny = dy / dist;
+ var basex = length * nx;
+ var basey = length * ny;
+ var floorx = width * ny/3;
+ var floory = -width * nx/3;
+
+ // Computes points
+ var p0x = p0.x - floorx / 2 + spacing * nx;
+ var p0y = p0.y - floory / 2 + spacing * ny;
+ var p1x = p0x + floorx;
+ var p1y = p0y + floory;
+ var p2x = p1x + basex;
+ var p2y = p1y + basey;
+ var p3x = p2x + floorx;
+ var p3y = p2y + floory;
+ // p4 not necessary
+ var p5x = p3x - 3 * floorx;
+ var p5y = p3y - 3 * floory;
+
+ canvas.begin();
+ canvas.moveTo(p0x, p0y);
+ canvas.lineTo(p1x, p1y);
+ canvas.lineTo(p2x, p2y);
+ canvas.lineTo(p3x, p3y);
+ canvas.lineTo(pe.x - spacing * nx, pe.y - spacing * ny);
+ canvas.lineTo(p5x, p5y);
+ canvas.lineTo(p5x + floorx, p5y + floory);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ this.shapes['cylinder'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ return false;
+ }
+ else
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+ var dy = Math.min(mxCylinder.prototype.maxHeight, Math.floor(h / 5));
+
+ canvas.begin();
+ canvas.moveTo(x, y + dy);
+ canvas.curveTo(x, y - dy / 3, x + w, y - dy / 3, x + w, y + dy);
+ canvas.lineTo(x + w, y + h - dy);
+ canvas.curveTo(x + w, y + h + dy / 3, x, y + h + dy / 3, x, y + h - dy);
+ canvas.close();
+ canvas.fillAndStroke();
+
+ canvas.begin();
+ canvas.moveTo(x, y + dy);
+ canvas.curveTo(x, y + 2 * dy, x + w, y + 2 * dy, x + w, y + dy);
+ canvas.stroke();
+ }
+ }
+ };
+
+ this.shapes['line'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ return false;
+ }
+ else
+ {
+ canvas.begin();
+
+ var mid = state.getCenterY();
+ canvas.moveTo(bounds.x, mid);
+ canvas.lineTo(bounds.x + bounds.width, mid);
+
+ canvas.stroke();
+ }
+ }
+ };
+
+ this.shapes['ellipse'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ canvas.ellipse(bounds.x, bounds.y, bounds.width, bounds.height);
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ this.shapes['doubleEllipse'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ if (background)
+ {
+ canvas.ellipse(x, y, w, h);
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+
+ var inset = Math.min(4, Math.min(w / 5, h / 5));
+ x += inset;
+ y += inset;
+ w -= 2 * inset;
+ h -= 2 * inset;
+
+ if (w > 0 && h > 0)
+ {
+ canvas.ellipse(x, y, w, h);
+ }
+
+ canvas.stroke();
+ }
+ }
+ };
+
+ this.shapes['triangle'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+ canvas.begin();
+ canvas.moveTo(x, y);
+ canvas.lineTo(x + w, y + h / 2);
+ canvas.lineTo(x, y + h);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ this.shapes['rhombus'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+ var hw = w / 2;
+ var hh = h / 2;
+
+ canvas.begin();
+ canvas.moveTo(x + hw, y);
+ canvas.lineTo(x + w, y + hh);
+ canvas.lineTo(x + hw, y + h);
+ canvas.lineTo(x, y + hh);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+
+ };
+
+ this.shapes['hexagon'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ canvas.begin();
+ canvas.moveTo(x + 0.25 * w, y);
+ canvas.lineTo(x + 0.75 * w, y);
+ canvas.lineTo(x + w, y + 0.5 * h);
+ canvas.lineTo(x + 0.75 * w, y + h);
+ canvas.lineTo(x + 0.25 * w, y + h);
+ canvas.lineTo(x, y + 0.5 * h);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ this.shapes['actor'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+ var width = w * 2 / 6;
+
+ canvas.begin();
+ canvas.moveTo(x, y + h);
+ canvas.curveTo(x, y + 3 * h / 5, x, y + 2 * h / 5, x + w / 2, y + 2 * h
+ / 5);
+ canvas.curveTo(x + w / 2 - width, y + 2 * h / 5, x + w / 2 - width, y, x
+ + w / 2, y);
+ canvas.curveTo(x + w / 2 + width, y, x + w / 2 + width, y + 2 * h / 5, x
+ + w / 2, y + 2 * h / 5);
+ canvas.curveTo(x + w, y + 2 * h / 5, x + w, y + 3 * h / 5, x + w, y + h);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ this.shapes['cloud'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ canvas.begin();
+ canvas.moveTo(x + 0.25 * w, y + 0.25 * h);
+ canvas.curveTo(x + 0.05 * w, y + 0.25 * h, x,
+ y + 0.5 * h, x + 0.16 * w, y + 0.55 * h);
+ canvas.curveTo(x, y + 0.66 * h, x + 0.18 * w,
+ y + 0.9 * h, x + 0.31 * w, y + 0.8 * h);
+ canvas.curveTo(x + 0.4 * w, y + h, x + 0.7 * w,
+ y + h, x + 0.8 * w, y + 0.8 * h);
+ canvas.curveTo(x + w, y + 0.8 * h, x + w,
+ y + 0.6 * h, x + 0.875 * w, y + 0.5 * h);
+ canvas.curveTo(x + w, y + 0.3 * h, x + 0.8 * w,
+ y + 0.1 * h, x + 0.625 * w, y + 0.2 * h);
+ canvas.curveTo(x + 0.5 * w, y + 0.05 * h,
+ x + 0.3 * w, y + 0.05 * h,
+ x + 0.25 * w, y + 0.25 * h);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+};
+
+/**
+ * Function: initMarkers
+ *
+ * Initializes the built-in markers.
+ */
+mxImageExport.prototype.initMarkers = function()
+{
+ this.markers = [];
+
+ var tmp = function(canvas, state, type, pe, unitX, unitY, size, source, sw)
+ {
+ // The angle of the forward facing arrow sides against the x axis is
+ // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+ // only half the strokewidth is processed ).
+ var endOffsetX = unitX * sw * 1.118;
+ var endOffsetY = unitY * sw * 1.118;
+
+ pe.x -= endOffsetX;
+ pe.y -= endOffsetY;
+
+ unitX = unitX * (size + sw);
+ unitY = unitY * (size + sw);
+
+ canvas.begin();
+ canvas.moveTo(pe.x, pe.y);
+ canvas.lineTo(pe.x - unitX - unitY / 2, pe.y - unitY + unitX / 2);
+
+ if (type == mxConstants.ARROW_CLASSIC)
+ {
+ canvas.lineTo(pe.x - unitX * 3 / 4, pe.y - unitY * 3 / 4);
+ }
+
+ canvas.lineTo(pe.x + unitY / 2 - unitX, pe.y - unitY - unitX / 2);
+ canvas.close();
+
+ var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL;
+
+ if (state.style[key] == 0)
+ {
+ canvas.stroke();
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+
+ var f = (type != mxConstants.ARROW_CLASSIC) ? 1 : 3 / 4;
+ return new mxPoint(-unitX * f - endOffsetX, -unitY * f - endOffsetY);
+ };
+
+ this.markers['classic'] = tmp;
+ this.markers['block'] = tmp;
+
+ this.markers['open'] = function(canvas, state, type, pe, unitX, unitY, size, source, sw)
+ {
+ // The angle of the forward facing arrow sides against the x axis is
+ // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+ // only half the strokewidth is processed ).
+ var endOffsetX = unitX * sw * 1.118;
+ var endOffsetY = unitY * sw * 1.118;
+
+ pe.x -= endOffsetX;
+ pe.y -= endOffsetY;
+
+ unitX = unitX * (size + sw);
+ unitY = unitY * (size + sw);
+
+ canvas.begin();
+ canvas.moveTo(pe.x - unitX - unitY / 2, pe.y - unitY + unitX / 2);
+ canvas.lineTo(pe.x, pe.y);
+ canvas.lineTo(pe.x + unitY / 2 - unitX, pe.y - unitY - unitX / 2);
+ canvas.stroke();
+
+ return new mxPoint(-endOffsetX * 2, -endOffsetY * 2);
+ };
+
+ this.markers['oval'] = function(canvas, state, type, pe, unitX, unitY, size, source, sw)
+ {
+ var a = size / 2;
+
+ canvas.ellipse(pe.x - a, pe.y - a, size, size);
+
+ var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL;
+
+ if (state.style[key] == 0)
+ {
+ canvas.stroke();
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+
+ return new mxPoint(-unitX / 2, -unitY / 2);
+ };
+
+ var tmp_diamond = function(canvas, state, type, pe, unitX, unitY, size, source, sw)
+ {
+ // The angle of the forward facing arrow sides against the x axis is
+ // 45 degrees, 1/sin(45) = 1.4142 / 2 = 0.7071 ( / 2 allows for
+ // only half the strokewidth is processed ). Or 0.9862 for thin diamond.
+ // Note these values and the tk variable below are dependent, update
+ // both together (saves trig hard coding it).
+ var swFactor = (type == mxConstants.ARROW_DIAMOND) ? 0.7071 : 0.9862;
+ var endOffsetX = unitX * sw * swFactor;
+ var endOffsetY = unitY * sw * swFactor;
+
+ unitX = unitX * (size + sw);
+ unitY = unitY * (size + sw);
+
+ pe.x -= endOffsetX;
+ pe.y -= endOffsetY;
+
+ // thickness factor for diamond
+ var tk = ((type == mxConstants.ARROW_DIAMOND) ? 2 : 3.4);
+
+ canvas.begin();
+ canvas.moveTo(pe.x, pe.y);
+ canvas.lineTo(pe.x - unitX / 2 - unitY / tk, pe.y + unitX / tk - unitY / 2);
+ canvas.lineTo(pe.x - unitX, pe.y - unitY);
+ canvas.lineTo(pe.x - unitX / 2 + unitY / tk, pe.y - unitY / 2 - unitX / tk);
+ canvas.close();
+
+ var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL;
+
+ if (state.style[key] == 0)
+ {
+ canvas.stroke();
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+
+ return new mxPoint(-endOffsetX - unitX, -endOffsetY - unitY);
+ };
+
+ this.markers['diamond'] = tmp_diamond;
+ this.markers['diamondThin'] = tmp_diamond;
+};
diff --git a/src/js/util/mxLog.js b/src/js/util/mxLog.js
new file mode 100644
index 0000000..c556e22
--- /dev/null
+++ b/src/js/util/mxLog.js
@@ -0,0 +1,410 @@
+/**
+ * $Id: mxLog.js,v 1.32 2012-11-12 09:40:59 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxLog =
+{
+ /**
+ * Class: mxLog
+ *
+ * A singleton class that implements a simple console.
+ *
+ * Variable: consoleName
+ *
+ * Specifies the name of the console window. Default is 'Console'.
+ */
+ consoleName: 'Console',
+
+ /**
+ * Variable: TRACE
+ *
+ * Specified if the output for <enter> and <leave> should be visible in the
+ * console. Default is false.
+ */
+ TRACE: false,
+
+ /**
+ * Variable: DEBUG
+ *
+ * Specifies if the output for <debug> should be visible in the console.
+ * Default is true.
+ */
+ DEBUG: true,
+
+ /**
+ * Variable: WARN
+ *
+ * Specifies if the output for <warn> should be visible in the console.
+ * Default is true.
+ */
+ WARN: true,
+
+ /**
+ * Variable: buffer
+ *
+ * Buffer for pre-initialized content.
+ */
+ buffer: '',
+
+ /**
+ * Function: init
+ *
+ * Initializes the DOM node for the console. This requires document.body to
+ * point to a non-null value. This is called from within <setVisible> if the
+ * log has not yet been initialized.
+ */
+ init: function()
+ {
+ if (mxLog.window == null && document.body != null)
+ {
+ var title = mxLog.consoleName + ' - mxGraph ' + mxClient.VERSION;
+
+ // Creates a table that maintains the layout
+ var table = document.createElement('table');
+ table.setAttribute('width', '100%');
+ table.setAttribute('height', '100%');
+
+ var tbody = document.createElement('tbody');
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+ td.style.verticalAlign = 'top';
+
+ // Adds the actual console as a textarea
+ mxLog.textarea = document.createElement('textarea');
+ mxLog.textarea.setAttribute('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')
+ {
+ var elt = mxLog.window.getElement();
+
+ var resizeHandler = function(sender, evt)
+ {
+ mxLog.textarea.style.height = Math.max(0, elt.offsetHeight - 70)+'px';
+ };
+
+ mxLog.window.addListener(mxEvent.RESIZE_END, resizeHandler);
+ mxLog.window.addListener(mxEvent.MAXIMIZE, resizeHandler);
+ mxLog.window.addListener(mxEvent.NORMALIZE, resizeHandler);
+
+ mxLog.textarea.style.height = '92px';
+ }
+ }
+ },
+
+ /**
+ * Function: info
+ *
+ * Writes the current navigator information to the console.
+ */
+ info: function()
+ {
+ mxLog.writeln(mxUtils.toString(navigator));
+ },
+
+ /**
+ * Function: addButton
+ *
+ * Adds a button to the console using the given label and function.
+ */
+ addButton: function(lab, funct)
+ {
+ var button = document.createElement('button');
+ mxUtils.write(button, lab);
+ mxEvent.addListener(button, 'click', funct);
+ mxLog.td.appendChild(button);
+ },
+
+ /**
+ * Function: isVisible
+ *
+ * Returns true if the console is visible.
+ */
+ isVisible: function()
+ {
+ if (mxLog.window != null)
+ {
+ return mxLog.window.isVisible();
+ }
+ return false;
+ },
+
+
+ /**
+ * Function: show
+ *
+ * Shows the console.
+ */
+ show: function()
+ {
+ mxLog.setVisible(true);
+ },
+
+ /**
+ * Function: setVisible
+ *
+ * Shows or hides the console.
+ */
+ setVisible: function(visible)
+ {
+ if (mxLog.window == null)
+ {
+ mxLog.init();
+ }
+
+ if (mxLog.window != null)
+ {
+ mxLog.window.setVisible(visible);
+ }
+ },
+
+ /**
+ * Function: enter
+ *
+ * Writes the specified string to the console
+ * if <TRACE> is true and returns the current
+ * time in milliseconds.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * var t0 = mxLog.enter('Hello');
+ * // Do something
+ * mxLog.leave('World!', t0);
+ * (end)
+ */
+ enter: function(string)
+ {
+ if (mxLog.TRACE)
+ {
+ mxLog.writeln('Entering '+string);
+
+ return new Date().getTime();
+ }
+ },
+
+ /**
+ * Function: leave
+ *
+ * Writes the specified string to the console
+ * if <TRACE> is true and computes the difference
+ * between the current time and t0 in milliseconds.
+ * See <enter> for an example.
+ */
+ leave: function(string, t0)
+ {
+ if (mxLog.TRACE)
+ {
+ var dt = (t0 != 0) ? ' ('+(new Date().getTime() - t0)+' ms)' : '';
+ mxLog.writeln('Leaving '+string+dt);
+ }
+ },
+
+ /**
+ * Function: debug
+ *
+ * Adds all arguments to the console if <DEBUG> is enabled.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * mxLog.debug('Hello, World!');
+ * (end)
+ */
+ debug: function()
+ {
+ if (mxLog.DEBUG)
+ {
+ mxLog.writeln.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Function: warn
+ *
+ * Adds all arguments to the console if <WARN> is enabled.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * mxLog.warn('Hello, World!');
+ * (end)
+ */
+ warn: function()
+ {
+ if (mxLog.WARN)
+ {
+ mxLog.writeln.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Function: write
+ *
+ * Adds the specified strings to the console.
+ */
+ write: function()
+ {
+ var string = '';
+
+ for (var i = 0; i < arguments.length; i++)
+ {
+ string += arguments[i];
+
+ if (i < arguments.length - 1)
+ {
+ string += ' ';
+ }
+ }
+
+ if (mxLog.textarea != null)
+ {
+ mxLog.textarea.value = mxLog.textarea.value + string;
+
+ // Workaround for no update in Presto 2.5.22 (Opera 10.5)
+ if (navigator.userAgent.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');
+ }
+
+};
diff --git a/src/js/util/mxMorphing.js b/src/js/util/mxMorphing.js
new file mode 100644
index 0000000..442143d
--- /dev/null
+++ b/src/js/util/mxMorphing.js
@@ -0,0 +1,239 @@
+/**
+ * $Id: mxMorphing.js,v 1.4 2010-06-03 13:37:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxMorphing
+ *
+ * Implements animation for morphing cells. Here is an example of
+ * using this class for animating the result of a layout algorithm:
+ *
+ * (code)
+ * graph.getModel().beginUpdate();
+ * try
+ * {
+ * var circleLayout = new mxCircleLayout(graph);
+ * circleLayout.execute(graph.getDefaultParent());
+ * }
+ * finally
+ * {
+ * var morph = new mxMorphing(graph);
+ * morph.addListener(mxEvent.DONE, function()
+ * {
+ * graph.getModel().endUpdate();
+ * });
+ *
+ * morph.startAnimation();
+ * }
+ * (end)
+ *
+ * Constructor: mxMorphing
+ *
+ * Constructs an animation.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * steps - Optional number of steps in the morphing animation. Default is 6.
+ * ease - Optional easing constant for the animation. Default is 1.5.
+ * delay - Optional delay between the animation steps. Passed to <mxAnimation>.
+ */
+function mxMorphing(graph, steps, ease, delay)
+{
+ mxAnimation.call(this, delay);
+ this.graph = graph;
+ this.steps = (steps != null) ? steps : 6;
+ this.ease = (ease != null) ? ease : 1.5;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxMorphing.prototype = new mxAnimation();
+mxMorphing.prototype.constructor = mxMorphing;
+
+/**
+ * Variable: graph
+ *
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxMorphing.prototype.graph = null;
+
+/**
+ * Variable: steps
+ *
+ * Specifies the maximum number of steps for the morphing.
+ */
+mxMorphing.prototype.steps = null;
+
+/**
+ * Variable: step
+ *
+ * Contains the current step.
+ */
+mxMorphing.prototype.step = 0;
+
+/**
+ * Variable: ease
+ *
+ * Ease-off for movement towards the given vector. Larger values are
+ * slower and smoother. Default is 4.
+ */
+mxMorphing.prototype.ease = null;
+
+/**
+ * Variable: cells
+ *
+ * Optional array of cells to be animated. If this is not specified
+ * then all cells are checked and animated if they have been moved
+ * in the current transaction.
+ */
+mxMorphing.prototype.cells = null;
+
+/**
+ * Function: updateAnimation
+ *
+ * Animation step.
+ */
+mxMorphing.prototype.updateAnimation = function()
+{
+ 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(cells[i], move, false);
+ }
+ }
+ else
+ {
+ // Animates all changed cells by using recursion to find
+ // the changed cells but not for the animation itself
+ this.animateCell(this.graph.getModel().getRoot(), move, true);
+ }
+
+ this.show(move);
+
+ if (move.isEmpty() ||
+ this.step++ >= this.steps)
+ {
+ this.stopAnimation();
+ }
+};
+
+/**
+ * Function: show
+ *
+ * Shows the changes in the given <mxCellStatePreview>.
+ */
+mxMorphing.prototype.show = function(move)
+{
+ move.show();
+};
+
+/**
+ * Function: animateCell
+ *
+ * Animates the given cell state using <mxCellStatePreview.moveState>.
+ */
+mxMorphing.prototype.animateCell = function(cell, move, recurse)
+{
+ var state = this.graph.getView().getState(cell);
+ var delta = null;
+
+ if (state != null)
+ {
+ // Moves the animated state from where it will be after the model
+ // change by subtracting the given delta vector from that location
+ delta = this.getDelta(state);
+
+ if (this.graph.getModel().isVertex(cell) &&
+ (delta.x != 0 || delta.y != 0))
+ {
+ var translate = this.graph.view.getTranslate();
+ var scale = this.graph.view.getScale();
+
+ delta.x += translate.x * scale;
+ delta.y += translate.y * scale;
+
+ move.moveState(state, -delta.x / this.ease, -delta.y / this.ease);
+ }
+ }
+
+ if (recurse && !this.stopRecursion(state, delta))
+ {
+ var childCount = this.graph.getModel().getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse);
+ }
+ }
+};
+
+/**
+ * Function: stopRecursion
+ *
+ * Returns true if the animation should not recursively find more
+ * deltas for children if the given parent state has been animated.
+ */
+mxMorphing.prototype.stopRecursion = function(state, delta)
+{
+ return delta != null && (delta.x != 0 || delta.y != 0);
+};
+
+/**
+ * Function: getDelta
+ *
+ * Returns the vector between the current rendered state and the future
+ * location of the state after the display will be updated.
+ */
+mxMorphing.prototype.getDelta = function(state)
+{
+ var origin = this.getOriginForCell(state.cell);
+ var translate = this.graph.getView().getTranslate();
+ var scale = this.graph.getView().getScale();
+ var current = new mxPoint(
+ state.x / scale - translate.x,
+ state.y / scale - translate.y);
+
+ return new mxPoint(
+ (origin.x - current.x) * scale,
+ (origin.y - current.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)
+ {
+ result = this.getOriginForCell(this.graph.getModel().getParent(cell));
+ var geo = this.graph.getCellGeometry(cell);
+
+ // TODO: Handle offset, relative geometries etc
+ if (geo != null)
+ {
+ 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;
+};
diff --git a/src/js/util/mxMouseEvent.js b/src/js/util/mxMouseEvent.js
new file mode 100644
index 0000000..e161d3a
--- /dev/null
+++ b/src/js/util/mxMouseEvent.js
@@ -0,0 +1,241 @@
+/**
+ * $Id: mxMouseEvent.js,v 1.20 2011-03-02 17:24:39 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxMouseEvent
+ *
+ * Base class for all mouse events in mxGraph. A listener for this event should
+ * implement the following methods:
+ *
+ * (code)
+ * graph.addMouseListener(
+ * {
+ * mouseDown: function(sender, evt)
+ * {
+ * mxLog.debug('mouseDown');
+ * },
+ * mouseMove: function(sender, evt)
+ * {
+ * mxLog.debug('mouseMove');
+ * },
+ * mouseUp: function(sender, evt)
+ * {
+ * mxLog.debug('mouseUp');
+ * }
+ * });
+ * (end)
+ *
+ * Constructor: mxMouseEvent
+ *
+ * Constructs a new event object for the given arguments.
+ *
+ * Parameters:
+ *
+ * evt - Native mouse event.
+ * state - Optional <mxCellState> under the mouse.
+ *
+ */
+function mxMouseEvent(evt, state)
+{
+ this.evt = evt;
+ this.state = state;
+};
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state of this event.
+ */
+mxMouseEvent.prototype.consumed = false;
+
+/**
+ * Variable: evt
+ *
+ * Holds the inner event object.
+ */
+mxMouseEvent.prototype.evt = null;
+
+/**
+ * Variable: graphX
+ *
+ * Holds the x-coordinate of the event in the graph. This value is set in
+ * <mxGraph.fireMouseEvent>.
+ */
+mxMouseEvent.prototype.graphX = null;
+
+/**
+ * Variable: graphY
+ *
+ * Holds the y-coordinate of the event in the graph. This value is set in
+ * <mxGraph.fireMouseEvent>.
+ */
+mxMouseEvent.prototype.graphY = null;
+
+/**
+ * Variable: state
+ *
+ * Holds the optional <mxCellState> associated with this event.
+ */
+mxMouseEvent.prototype.state = null;
+
+/**
+ * Function: getEvent
+ *
+ * Returns <evt>.
+ */
+mxMouseEvent.prototype.getEvent = function()
+{
+ return this.evt;
+};
+
+/**
+ * Function: getSource
+ *
+ * Returns the target DOM element using <mxEvent.getSource> for <evt>.
+ */
+mxMouseEvent.prototype.getSource = function()
+{
+ return mxEvent.getSource(this.evt);
+};
+
+/**
+ * Function: isSource
+ *
+ * Returns true if the given <mxShape> is the source of <evt>.
+ */
+mxMouseEvent.prototype.isSource = function(shape)
+{
+ if (shape != null)
+ {
+ var source = this.getSource();
+
+ while (source != null)
+ {
+ if (source == shape.node)
+ {
+ return true;
+ }
+
+ source = source.parentNode;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Function: getX
+ *
+ * Returns <evt.clientX>.
+ */
+mxMouseEvent.prototype.getX = function()
+{
+ return mxEvent.getClientX(this.getEvent());
+};
+
+/**
+ * Function: getY
+ *
+ * Returns <evt.clientY>.
+ */
+mxMouseEvent.prototype.getY = function()
+{
+ return mxEvent.getClientY(this.getEvent());
+};
+
+/**
+ * Function: getGraphX
+ *
+ * Returns <graphX>.
+ */
+mxMouseEvent.prototype.getGraphX = function()
+{
+ return this.graphX;
+};
+
+/**
+ * Function: getGraphY
+ *
+ * Returns <graphY>.
+ */
+mxMouseEvent.prototype.getGraphY = function()
+{
+ return this.graphY;
+};
+
+/**
+ * Function: getState
+ *
+ * Returns <state>.
+ */
+mxMouseEvent.prototype.getState = function()
+{
+ return this.state;
+};
+
+/**
+ * Function: getCell
+ *
+ * Returns the <mxCell> in <state> is not null.
+ */
+mxMouseEvent.prototype.getCell = function()
+{
+ var state = this.getState();
+
+ if (state != null)
+ {
+ return state.cell;
+ }
+
+ return null;
+};
+
+/**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the event is a popup trigger.
+ */
+mxMouseEvent.prototype.isPopupTrigger = function()
+{
+ return mxEvent.isPopupTrigger(this.getEvent());
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns <consumed>.
+ */
+mxMouseEvent.prototype.isConsumed = function()
+{
+ return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Sets <consumed> to true and invokes preventDefault on the native event
+ * if such a method is defined. This is used mainly to avoid the cursor from
+ * being changed to a text cursor in Webkit. You can use the preventDefault
+ * flag to disable this functionality.
+ *
+ * Parameters:
+ *
+ * preventDefault - Specifies if the native event should be canceled. Default
+ * is true.
+ */
+mxMouseEvent.prototype.consume = function(preventDefault)
+{
+ preventDefault = (preventDefault != null) ? preventDefault : true;
+
+ if (preventDefault && this.evt.preventDefault)
+ {
+ this.evt.preventDefault();
+ }
+
+ // Workaround for images being dragged in IE
+ this.evt.returnValue = false;
+
+ // Sets local consumed state
+ this.consumed = true;
+};
diff --git a/src/js/util/mxObjectIdentity.js b/src/js/util/mxObjectIdentity.js
new file mode 100644
index 0000000..778a4ea
--- /dev/null
+++ b/src/js/util/mxObjectIdentity.js
@@ -0,0 +1,59 @@
+/**
+ * $Id: mxObjectIdentity.js,v 1.8 2010-01-02 09:45:14 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxObjectIdentity =
+{
+ /**
+ * Class: mxObjectIdentity
+ *
+ * Identity for JavaScript objects. This is implemented using a simple
+ * incremeting counter which is stored in each object under <ID_NAME>.
+ *
+ * The identity for an object does not change during its lifecycle.
+ *
+ * Variable: FIELD_NAME
+ *
+ * Name of the field to be used to store the object ID. Default is
+ * '_mxObjectId'.
+ */
+ FIELD_NAME: 'mxObjectId',
+
+ /**
+ * Variable: counter
+ *
+ * Current counter for objects.
+ */
+ counter: 0,
+
+ /**
+ * Function: get
+ *
+ * Returns the object id for the given object.
+ */
+ get: function(obj)
+ {
+ if (typeof(obj) == 'object' &&
+ obj[mxObjectIdentity.FIELD_NAME] == null)
+ {
+ var ctor = mxUtils.getFunctionName(obj.constructor);
+ obj[mxObjectIdentity.FIELD_NAME] = ctor+'#'+mxObjectIdentity.counter++;
+ }
+
+ return obj[mxObjectIdentity.FIELD_NAME];
+ },
+
+ /**
+ * Function: clear
+ *
+ * Removes the object id from the given object.
+ */
+ clear: function(obj)
+ {
+ if (typeof(obj) == 'object')
+ {
+ delete obj[mxObjectIdentity.FIELD_NAME];
+ }
+ }
+
+};
diff --git a/src/js/util/mxPanningManager.js b/src/js/util/mxPanningManager.js
new file mode 100644
index 0000000..9f9f349
--- /dev/null
+++ b/src/js/util/mxPanningManager.js
@@ -0,0 +1,262 @@
+/**
+ * $Id: mxPanningManager.js,v 1.7 2012-06-13 06:46:37 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * 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);
+
+ // Stops scrolling on every mouseup anywhere in the document
+ mxEvent.addListener(document, 'mouseup', mxUtils.bind(this, function()
+ {
+ if (this.active)
+ {
+ this.stop();
+ }
+ }));
+
+ 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);
+ };
+};
+
+/**
+ * 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;
diff --git a/src/js/util/mxPath.js b/src/js/util/mxPath.js
new file mode 100644
index 0000000..57efe74
--- /dev/null
+++ b/src/js/util/mxPath.js
@@ -0,0 +1,314 @@
+/**
+ * $Id: mxPath.js,v 1.24 2012-06-13 17:31:32 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPath
+ *
+ * An abstraction for creating VML and SVG paths. See <mxActor> for using this
+ * object inside an <mxShape> for painting cells.
+ *
+ * Constructor: mxPath
+ *
+ * Constructs a path for the given format, which is one of svg or vml.
+ *
+ * Parameters:
+ *
+ * format - String specifying the <format>. May be one of vml or svg
+ * (default).
+ */
+function mxPath(format)
+{
+ this.format = format;
+ this.path = [];
+ this.translate = new mxPoint(0, 0);
+};
+
+/**
+ * Variable: format
+ *
+ * Defines the format for the output of this path. Possible values are
+ * svg and vml.
+ */
+mxPath.prototype.format = null;
+
+/**
+ * Variable: translate
+ *
+ * <mxPoint> that specifies the translation of the complete path.
+ */
+mxPath.prototype.translate = null;
+
+/**
+ * Variable: scale
+ *
+ * Number that specifies the translation of the path.
+ */
+mxPath.prototype.scale = 1;
+
+/**
+ * Variable: path
+ *
+ * Contains the textual representation of the path as an array.
+ */
+mxPath.prototype.path = null;
+
+/**
+ * Function: isVml
+ *
+ * Returns true if <format> is vml.
+ */
+mxPath.prototype.isVml = function()
+{
+ return this.format == 'vml';
+};
+
+/**
+ * Function: getPath
+ *
+ * Returns string that represents the path in <format>.
+ */
+mxPath.prototype.getPath = function()
+{
+ return this.path.join('');
+};
+
+/**
+ * Function: setTranslate
+ *
+ * Set the global translation of this path, that is, the origin of the
+ * coordinate system.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the new origin.
+ * y - Y-coordinate of the new origin.
+ */
+mxPath.prototype.setTranslate = function(x, y)
+{
+ this.translate = new mxPoint(x, y);
+};
+
+/**
+ * Function: moveTo
+ *
+ * Moves the cursor to (x, y).
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the new cursor location.
+ * y - Y-coordinate of the new cursor location.
+ */
+mxPath.prototype.moveTo = function(x, y)
+{
+ x += this.translate.x;
+ y += this.translate.y;
+
+ x *= this.scale;
+ y *= this.scale;
+
+ if (this.isVml())
+ {
+ this.path.push('m ', Math.round(x), ' ', Math.round(y), ' ');
+ }
+ else
+ {
+ this.path.push('M ', x, ' ', y, ' ');
+ }
+};
+
+/**
+ * Function: lineTo
+ *
+ * Draws a straight line from the current poin to (x, y).
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the endpoint.
+ * y - Y-coordinate of the endpoint.
+ */
+mxPath.prototype.lineTo = function(x, y)
+{
+ x += this.translate.x;
+ y += this.translate.y;
+
+ x *= this.scale;
+ y *= this.scale;
+
+ if (this.isVml())
+ {
+ this.path.push('l ', Math.round(x), ' ', Math.round(y), ' ');
+ }
+ else
+ {
+ this.path.push('L ', x, ' ', y, ' ');
+ }
+};
+
+/**
+ * Function: quadTo
+ *
+ * Draws a quadratic Bézier curve from the current point to (x, y) using
+ * (x1, y1) as the control point.
+ *
+ * Parameters:
+ *
+ * x1 - X-coordinate of the control point.
+ * y1 - Y-coordinate of the control point.
+ * x - X-coordinate of the endpoint.
+ * y - Y-coordinate of the endpoint.
+ */
+mxPath.prototype.quadTo = function(x1, y1, x, y)
+{
+ x1 += this.translate.x;
+ y1 += this.translate.y;
+
+ x1 *= this.scale;
+ y1 *= this.scale;
+
+ x += this.translate.x;
+ y += this.translate.y;
+
+ x *= this.scale;
+ y *= this.scale;
+
+ if (this.isVml())
+ {
+ this.path.push('c ', Math.round(x1), ' ', Math.round(y1), ' ', Math.round(x), ' ',
+ Math.round(y), ' ', Math.round(x), ' ', Math.round(y), ' ');
+ }
+ else
+ {
+ this.path.push('Q ', x1, ' ', y1, ' ', x, ' ', y, ' ');
+ }
+};
+
+/**
+ * Function: curveTo
+ *
+ * Draws a cubic Bézier curve from the current point to (x, y) using
+ * (x1, y1) as the control point at the beginning of the curve and (x2, y2)
+ * as the control point at the end of the curve.
+ *
+ * Parameters:
+ *
+ * x1 - X-coordinate of the first control point.
+ * y1 - Y-coordinate of the first control point.
+ * x2 - X-coordinate of the second control point.
+ * y2 - Y-coordinate of the second control point.
+ * x - X-coordinate of the endpoint.
+ * y - Y-coordinate of the endpoint.
+ */
+mxPath.prototype.curveTo = function(x1, y1, x2, y2, x, y)
+{
+ x1 += this.translate.x;
+ y1 += this.translate.y;
+
+ x1 *= this.scale;
+ y1 *= this.scale;
+
+ x2 += this.translate.x;
+ y2 += this.translate.y;
+
+ x2 *= this.scale;
+ y2 *= this.scale;
+
+ x += this.translate.x;
+ y += this.translate.y;
+
+ x *= this.scale;
+ y *= this.scale;
+
+ if (this.isVml())
+ {
+ this.path.push('c ', Math.round(x1), ' ', Math.round(y1), ' ', Math.round(x2),
+ ' ', Math.round(y2), ' ', Math.round(x), ' ', Math.round(y), ' ');
+ }
+ else
+ {
+ this.path.push('C ', x1, ' ', y1, ' ', x2,
+ ' ', y2, ' ', x, ' ', y, ' ');
+ }
+};
+
+/**
+ * Function: ellipse
+ *
+ * Adds the given ellipse. Some implementations may require the path to be
+ * closed after this operation.
+ */
+mxPath.prototype.ellipse = function(x, y, w, h)
+{
+ x += this.translate.x;
+ y += this.translate.y;
+ x *= this.scale;
+ y *= this.scale;
+
+ if (this.isVml())
+ {
+ this.path.push('at ', Math.round(x), ' ', Math.round(y), ' ', Math.round(x + w), ' ', Math.round(y + h), ' ',
+ Math.round(x), ' ', Math.round(y + h / 2), ' ', Math.round(x), ' ', Math.round(y + h / 2));
+ }
+ else
+ {
+ var startX = x;
+ var startY = y + h/2;
+ var endX = x + w;
+ var endY = y + h/2;
+ var r1 = w/2;
+ var r2 = h/2;
+ this.path.push('M ', startX, ' ', startY, ' ');
+ this.path.push('A ', r1, ' ', r2, ' 0 1 0 ', endX, ' ', endY, ' ');
+ this.path.push('A ', r1, ' ', r2, ' 0 1 0 ', startX, ' ', startY);
+ }
+};
+
+/**
+ * Function: addPath
+ *
+ * Adds the given path.
+ */
+mxPath.prototype.addPath = function(path)
+{
+ this.path = this.path.concat(path.path);
+};
+
+/**
+ * Function: write
+ *
+ * Writes directly into the path. This bypasses all conversions.
+ */
+mxPath.prototype.write = function(string)
+{
+ this.path.push(string, ' ');
+};
+
+/**
+ * Function: end
+ *
+ * Ends the path.
+ */
+mxPath.prototype.end = function()
+{
+ if (this.format == 'vml')
+ {
+ this.path.push('e');
+ }
+};
+
+/**
+ * Function: close
+ *
+ * Closes the path.
+ */
+mxPath.prototype.close = function()
+{
+ if (this.format == 'vml')
+ {
+ this.path.push('x e');
+ }
+ else
+ {
+ this.path.push('Z');
+ }
+};
diff --git a/src/js/util/mxPoint.js b/src/js/util/mxPoint.js
new file mode 100644
index 0000000..e029a29
--- /dev/null
+++ b/src/js/util/mxPoint.js
@@ -0,0 +1,55 @@
+/**
+ * $Id: mxPoint.js,v 1.12 2010-01-02 09:45:14 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPoint
+ *
+ * Implements a 2-dimensional vector with double precision coordinates.
+ *
+ * Constructor: mxPoint
+ *
+ * Constructs a new point for the optional x and y coordinates. If no
+ * coordinates are given, then the default values for <x> and <y> are used.
+ */
+function mxPoint(x, y)
+{
+ this.x = (x != null) ? x : 0;
+ this.y = (y != null) ? y : 0;
+};
+
+/**
+ * Variable: x
+ *
+ * Holds the x-coordinate of the point. Default is 0.
+ */
+mxPoint.prototype.x = null;
+
+/**
+ * Variable: y
+ *
+ * Holds the y-coordinate of the point. Default is 0.
+ */
+mxPoint.prototype.y = null;
+
+/**
+ * Function: equals
+ *
+ * Returns true if the given object equals this rectangle.
+ */
+mxPoint.prototype.equals = function(obj)
+{
+ return obj.x == this.x &&
+ obj.y == this.y;
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxPoint.prototype.clone = function()
+{
+ // Handles subclasses as well
+ return mxUtils.clone(this);
+};
diff --git a/src/js/util/mxPopupMenu.js b/src/js/util/mxPopupMenu.js
new file mode 100644
index 0000000..b188cb6
--- /dev/null
+++ b/src/js/util/mxPopupMenu.js
@@ -0,0 +1,574 @@
+/**
+ * $Id: mxPopupMenu.js,v 1.37 2012-04-22 10:16:23 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPopupMenu
+ *
+ * Event handler that pans and creates popupmenus. To use the left
+ * mousebutton for panning without interfering with cell moving and
+ * resizing, use <isUseLeftButton> and <isIgnoreCell>. For grid size
+ * steps while panning, use <useGrid>. This handler is built-into
+ * <mxGraph.panningHandler> and enabled using <mxGraph.setPanning>.
+ *
+ * Constructor: mxPopupMenu
+ *
+ * Constructs an event handler that creates a popupmenu. The
+ * event handler is not installed anywhere in this ctor.
+ *
+ * Event: mxEvent.SHOW
+ *
+ * Fires after the menu has been shown in <popup>.
+ */
+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 1006.
+ */
+mxPopupMenu.prototype.zIndex = 10006;
+
+/**
+ * Variable: factoryMethod
+ *
+ * Function that is used to create the popup menu. The function takes the
+ * current panning handler, the <mxCell> under the mouse and the mouse
+ * event that triggered the call as arguments.
+ */
+mxPopupMenu.prototype.factoryMethod = null;
+
+/**
+ * Variable: useLeftButtonForPopup
+ *
+ * Specifies if popupmenus should be activated by clicking the left mouse
+ * button. Default is false.
+ */
+mxPopupMenu.prototype.useLeftButtonForPopup = false;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxPopupMenu.prototype.enabled = true;
+
+/**
+ * Variable: itemCount
+ *
+ * Contains the number of times <addItem> has been called for a new menu.
+ */
+mxPopupMenu.prototype.itemCount = 0;
+
+/**
+ * Variable: autoExpand
+ *
+ * Specifies if submenus should be expanded on mouseover. Default is false.
+ */
+mxPopupMenu.prototype.autoExpand = false;
+
+/**
+ * Variable: smartSeparators
+ *
+ * Specifies if separators should only be added if a menu item follows them.
+ * Default is false.
+ */
+mxPopupMenu.prototype.smartSeparators = false;
+
+/**
+ * Variable: labels
+ *
+ * Specifies if any labels should be visible. Default is true.
+ */
+mxPopupMenu.prototype.labels = true;
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPopupMenu.prototype.init = function()
+{
+ // Adds the inner table
+ this.table = document.createElement('table');
+ this.table.className = 'mxPopupMenu';
+
+ this.tbody = document.createElement('tbody');
+ this.table.appendChild(this.tbody);
+
+ // Adds the outer div
+ this.div = document.createElement('div');
+ this.div.className = 'mxPopupMenu';
+ this.div.style.display = 'inline';
+ this.div.style.zIndex = this.zIndex;
+ this.div.appendChild(this.table);
+
+ // Disables the context menu on the outer div
+ mxEvent.disableContextMenu(this.div);
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxPopupMenu.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ */
+mxPopupMenu.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the given event is a popupmenu trigger for the optional
+ * given cell.
+ *
+ * Parameters:
+ *
+ * me - <mxMouseEvent> that represents the mouse event.
+ */
+mxPopupMenu.prototype.isPopupTrigger = function(me)
+{
+ return me.isPopupTrigger() || (this.useLeftButtonForPopup &&
+ mxEvent.isLeftMouseButton(me.getEvent()));
+};
+
+/**
+ * Function: addItem
+ *
+ * Adds the given item to the given parent item. If no parent item is specified
+ * then the item is added to the top-level menu. The return value may be used
+ * as the parent argument, ie. as a submenu item. The return value is the table
+ * row that represents the item.
+ *
+ * Paramters:
+ *
+ * title - String that represents the title of the menu item.
+ * image - Optional URL for the image icon.
+ * funct - Function associated that takes a mouseup or touchend event.
+ * parent - Optional item returned by <addItem>.
+ * iconCls - Optional string that represents the CSS class for the image icon.
+ * IconsCls is ignored if image is given.
+ * enabled - Optional boolean indicating if the item is enabled. Default is true.
+ */
+mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled)
+{
+ 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) ? ' disabled' : '');
+ mxUtils.write(col2, title);
+ col2.align = 'left';
+ tr.appendChild(col2);
+
+ var col3 = document.createElement('td');
+ col3.className = 'mxPopupMenuItem' +
+ ((enabled != null && !enabled) ? ' disabled' : '');
+ col3.style.paddingRight = '6px';
+ col3.style.textAlign = 'right';
+
+ tr.appendChild(col3);
+
+ if (parent.div == null)
+ {
+ this.createSubmenu(parent);
+ }
+ }
+
+ parent.tbody.appendChild(tr);
+
+ if (enabled == null || enabled)
+ {
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ // Consumes the event on mouse down
+ mxEvent.addListener(tr, md, 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;
+ }
+ }
+
+ mxEvent.consume(evt);
+ }));
+
+ mxEvent.addListener(tr, mm, 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';
+ }));
+
+ mxEvent.addListener(tr, mu, 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();
+ }
+
+ 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;
+};
+
+/**
+ * Function: createSubmenu
+ *
+ * Creates the nodes required to add submenu items inside the given parent
+ * item. This is called in <addItem> if a parent item is used for the first
+ * time. This adds various DOM nodes and a <submenuImage> to the parent.
+ *
+ * Parameters:
+ *
+ * parent - An item returned by <addItem>.
+ */
+mxPopupMenu.prototype.createSubmenu = function(parent)
+{
+ parent.table = document.createElement('table');
+ parent.table.className = 'mxPopupMenu';
+
+ parent.tbody = document.createElement('tbody');
+ parent.table.appendChild(parent.tbody);
+
+ parent.div = document.createElement('div');
+ parent.div.className = 'mxPopupMenu';
+
+ parent.div.style.position = 'absolute';
+ parent.div.style.display = 'inline';
+ parent.div.style.zIndex = this.zIndex;
+
+ parent.div.appendChild(parent.table);
+
+ var img = document.createElement('img');
+ img.setAttribute('src', this.submenuImage);
+
+ // Last column of the submenu item in the parent menu
+ td = parent.firstChild.nextSibling.nextSibling;
+ td.appendChild(img);
+};
+
+/**
+ * Function: showSubmenu
+ *
+ * Shows the submenu inside the given parent row.
+ */
+mxPopupMenu.prototype.showSubmenu = function(parent, row)
+{
+ if (row.div != null)
+ {
+ row.div.style.left = (parent.div.offsetLeft +
+ row.offsetLeft+row.offsetWidth - 1) + 'px';
+ row.div.style.top = (parent.div.offsetTop+row.offsetTop) + 'px';
+ document.body.appendChild(row.div);
+
+ // Moves the submenu to the left side if there is no space
+ var left = parseInt(row.div.offsetLeft);
+ var width = parseInt(row.div.offsetWidth);
+
+ var b = document.body;
+ var d = document.documentElement;
+
+ var right = (b.scrollLeft || d.scrollLeft) + (b.clientWidth || d.clientWidth);
+
+ if (left + width > right)
+ {
+ row.div.style.left = (parent.div.offsetLeft - width +
+ ((mxClient.IS_IE) ? 6 : -6)) + 'px';
+ }
+
+ mxUtils.fit(row.div);
+ }
+};
+
+/**
+ * Function: addSeparator
+ *
+ * Adds a horizontal separator in the given parent item or the top-level menu
+ * if no parent is specified.
+ *
+ * Parameters:
+ *
+ * parent - Optional item returned by <addItem>.
+ * force - Optional boolean to ignore <smartSeparators>. Default is false.
+ */
+mxPopupMenu.prototype.addSeparator = function(parent, force)
+{
+ parent = parent || this;
+
+ if (this.smartSeparators && !force)
+ {
+ parent.willAddSeparator = true;
+ }
+ else if (parent.tbody != null)
+ {
+ parent.willAddSeparator = false;
+ var tr = document.createElement('tr');
+
+ var col1 = document.createElement('td');
+ col1.className = 'mxPopupMenuIcon';
+ col1.style.padding = '0 0 0 0px';
+
+ tr.appendChild(col1);
+
+ var col2 = document.createElement('td');
+ col2.style.padding = '0 0 0 0px';
+ col2.setAttribute('colSpan', '2');
+
+ var hr = document.createElement('hr');
+ hr.setAttribute('size', '1');
+ col2.appendChild(hr);
+
+ tr.appendChild(col2);
+
+ parent.tbody.appendChild(tr);
+ }
+};
+
+/**
+ * Function: popup
+ *
+ * Shows the popup menu for the given event and cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.panningHandler.popup = function(x, y, cell, evt)
+ * {
+ * mxUtils.alert('Hello, World!');
+ * }
+ * (end)
+ */
+mxPopupMenu.prototype.popup = function(x, y, cell, evt)
+{
+ if (this.div != null && this.tbody != null && this.factoryMethod != null)
+ {
+ this.div.style.left = x + 'px';
+ this.div.style.top = y + 'px';
+
+ // Removes all child nodes from the existing menu
+ while (this.tbody.firstChild != null)
+ {
+ mxEvent.release(this.tbody.firstChild);
+ this.tbody.removeChild(this.tbody.firstChild);
+ }
+
+ this.itemCount = 0;
+ this.factoryMethod(this, cell, evt);
+
+ if (this.itemCount > 0)
+ {
+ this.showMenu();
+ this.fireEvent(new mxEventObject(mxEvent.SHOW));
+ }
+ }
+};
+
+/**
+ * Function: isMenuShowing
+ *
+ * Returns true if the menu is showing.
+ */
+mxPopupMenu.prototype.isMenuShowing = function()
+{
+ return this.div != null && this.div.parentNode == document.body;
+};
+
+/**
+ * Function: showMenu
+ *
+ * Shows the menu.
+ */
+mxPopupMenu.prototype.showMenu = function()
+{
+ // Disables filter-based shadow in IE9 standards mode
+ if (document.documentMode >= 9)
+ {
+ this.div.style.filter = 'none';
+ }
+
+ // Fits the div inside the viewport
+ 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;
+ }
+};
+
+/**
+ * Function: hideSubmenu
+ *
+ * Removes all submenus inside the given parent.
+ *
+ * Parameters:
+ *
+ * parent - An item returned by <addItem>.
+ */
+mxPopupMenu.prototype.hideSubmenu = function(parent)
+{
+ if (parent.activeRow != null)
+ {
+ this.hideSubmenu(parent.activeRow);
+
+ if (parent.activeRow.div.parentNode != null)
+ {
+ parent.activeRow.div.parentNode.removeChild(parent.activeRow.div);
+ }
+
+ parent.activeRow = null;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPopupMenu.prototype.destroy = function()
+{
+ if (this.div != null)
+ {
+ mxEvent.release(this.div);
+
+ if (this.div.parentNode != null)
+ {
+ this.div.parentNode.removeChild(this.div);
+ }
+
+ this.div = null;
+ }
+};
diff --git a/src/js/util/mxRectangle.js b/src/js/util/mxRectangle.js
new file mode 100644
index 0000000..035abf5
--- /dev/null
+++ b/src/js/util/mxRectangle.js
@@ -0,0 +1,134 @@
+/**
+ * $Id: mxRectangle.js,v 1.17 2010-12-08 12:46:03 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxRectangle
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ *
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxRectangle(x, y, width, height)
+{
+ mxPoint.call(this, x, y);
+
+ this.width = (width != null) ? width : 0;
+ this.height = (height != null) ? height : 0;
+};
+
+/**
+ * Extends mxPoint.
+ */
+mxRectangle.prototype = new mxPoint();
+mxRectangle.prototype.constructor = mxRectangle;
+
+/**
+ * Variable: width
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxRectangle.prototype.width = null;
+
+/**
+ * Variable: height
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxRectangle.prototype.height = null;
+
+/**
+ * Function: setRect
+ *
+ * Sets this rectangle to the specified values
+ */
+mxRectangle.prototype.setRect = function(x, y, w, h)
+{
+ this.x = x;
+ this.y = y;
+ this.width = w;
+ this.height = h;
+};
+
+/**
+ * Function: getCenterX
+ *
+ * Returns the x-coordinate of the center point.
+ */
+mxRectangle.prototype.getCenterX = function ()
+{
+ return this.x + this.width/2;
+};
+
+/**
+ * Function: getCenterY
+ *
+ * Returns the y-coordinate of the center point.
+ */
+mxRectangle.prototype.getCenterY = function ()
+{
+ return this.y + this.height/2;
+};
+
+/**
+ * Function: add
+ *
+ * Adds the given rectangle to this rectangle.
+ */
+mxRectangle.prototype.add = function(rect)
+{
+ if (rect != null)
+ {
+ var minX = Math.min(this.x, rect.x);
+ var minY = Math.min(this.y, rect.y);
+ var maxX = Math.max(this.x + this.width, rect.x + rect.width);
+ var maxY = Math.max(this.y + this.height, rect.y + rect.height);
+
+ this.x = minX;
+ this.y = minY;
+ this.width = maxX - minX;
+ this.height = maxY - minY;
+ }
+};
+
+/**
+ * Function: 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 <mxPoint>.
+ */
+mxRectangle.prototype.getPoint = function()
+{
+ return new mxPoint(this.x, this.y);
+};
+
+/**
+ * Function: equals
+ *
+ * Returns true if the given object equals this rectangle.
+ */
+mxRectangle.prototype.equals = function(obj)
+{
+ return obj.x == this.x &&
+ obj.y == this.y &&
+ obj.width == this.width &&
+ obj.height == this.height;
+};
diff --git a/src/js/util/mxResources.js b/src/js/util/mxResources.js
new file mode 100644
index 0000000..0969ebe
--- /dev/null
+++ b/src/js/util/mxResources.js
@@ -0,0 +1,366 @@
+/**
+ * $Id: mxResources.js,v 1.32 2012-10-26 13:36:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxResources =
+{
+ /**
+ * Class: mxResources
+ *
+ * Implements internationalization. You can provide any number of
+ * resource files on the server using the following format for the
+ * filename: name[-en].properties. The en stands for any lowercase
+ * 2-character language shortcut (eg. de for german, fr for french).
+ *
+ * If the optional language extension is omitted, then the file is used as a
+ * default resource which is loaded in all cases. If a properties file for a
+ * specific language exists, then it is used to override the settings in the
+ * default resource. All entries in the file are of the form key=value. The
+ * values may then be accessed in code via <get>. Lines without
+ * equal signs in the properties files are ignored.
+ *
+ * Resource files may either be added programmatically using
+ * <add> or via a resource tag in the UI section of the
+ * editor configuration file, eg:
+ *
+ * (code)
+ * <mxEditor>
+ * <ui>
+ * <resource basename="examples/resources/mxWorkflow"/>
+ * (end)
+ *
+ * The above element will load examples/resources/mxWorkflow.properties as well
+ * as the language specific file for the current language, if it exists.
+ *
+ * Values may contain placeholders of the form {1}...{n} where each placeholder
+ * is replaced with the value of the corresponding array element in the params
+ * argument passed to <mxResources.get>. The placeholder {1} maps to the first
+ * element in the array (at index 0).
+ *
+ * See <mxClient.language> for more information on specifying the default
+ * language or disabling all loading of resources.
+ *
+ * Lines that start with a # sign will be ignored.
+ *
+ * Special characters
+ *
+ * To use unicode characters, use the standard notation (eg. \u8fd1) or %u as a
+ * prefix (eg. %u20AC will display a Euro sign). For normal hex encoded strings,
+ * use % as a prefix, eg. %F6 will display a � (&ouml;).
+ *
+ * See <resourcesEncoded> to disable this. If you disable this, make sure that
+ * your files are UTF-8 encoded.
+ *
+ * Variable: resources
+ *
+ * Associative array that maps from keys to values.
+ */
+ resources: [],
+
+ /**
+ * Variable: extension
+ *
+ * Specifies the extension used for language files. Default is '.properties'.
+ */
+ extension: '.properties',
+
+ /**
+ * Variable: resourcesEncoded
+ *
+ * Specifies whether or not values in resource files are encoded with \u or
+ * percentage. Default is true.
+ */
+ resourcesEncoded: true,
+
+ /**
+ * 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: isBundleSupported
+ *
+ * Hook for subclassers to disable support for a given language. This
+ * implementation always returns true.
+ *
+ * Parameters:
+ *
+ * basename - The basename for which the file should be loaded.
+ * lan - The current language.
+ */
+ isLanguageSupported: function(lan)
+ {
+ if (mxClient.languages != null)
+ {
+ return mxUtils.indexOf(mxClient.languages, lan) >= 0;
+ }
+
+ return true;
+ },
+
+ /**
+ * Function: getDefaultBundle
+ *
+ * Hook for subclassers to return the URL for the special bundle. This
+ * implementation returns basename + <extension> or null if
+ * <loadDefaultBundle> is false.
+ *
+ * Parameters:
+ *
+ * basename - The basename for which the file should be loaded.
+ * lan - The current language.
+ */
+ getDefaultBundle: function(basename, lan)
+ {
+ if (mxResources.loadDefaultBundle || !mxResources.isLanguageSupported(lan))
+ {
+ return basename + mxResources.extension;
+ }
+ else
+ {
+ return null;
+ }
+ },
+
+ /**
+ * Function: getSpecialBundle
+ *
+ * Hook for subclassers to return the URL for the special bundle. This
+ * implementation returns basename + '_' + lan + <extension> or null if
+ * <loadSpecialBundle> is false or lan equals <mxClient.defaultLanguage>.
+ *
+ * If <mxResources.languages> is not null and <mxClient.language> contains
+ * a dash, then this method checks if <isLanguageSupported> returns true
+ * for the full language (including the dash). If that returns false the
+ * first part of the language (up to the dash) will be tried as an extension.
+ *
+ * If <mxResources.language> is null then the first part of the language is
+ * used to maintain backwards compatibility.
+ *
+ * Parameters:
+ *
+ * basename - The basename for which the file should be loaded.
+ * lan - The language for which the file should be loaded.
+ */
+ getSpecialBundle: function(basename, lan)
+ {
+ if (mxClient.languages == null || !this.isLanguageSupported(lan))
+ {
+ var dash = lan.indexOf('-');
+
+ if (dash > 0)
+ {
+ lan = lan.substring(0, dash);
+ }
+ }
+
+ if (mxResources.loadSpecialBundle && mxResources.isLanguageSupported(lan) && lan != mxClient.defaultLanguage)
+ {
+ return basename + '_' + lan + mxResources.extension;
+ }
+ else
+ {
+ return null;
+ }
+ },
+
+ /**
+ * Function: add
+ *
+ * Adds the default and current language properties
+ * file for the specified basename. Existing keys
+ * are overridden as new files are added.
+ *
+ * Example:
+ *
+ * At application startup, additional resources may be
+ * added using the following code:
+ *
+ * (code)
+ * mxResources.add('resources/editor');
+ * (end)
+ */
+ add: function(basename, lan)
+ {
+ lan = (lan != null) ? lan : mxClient.language.toLowerCase();
+
+ if (lan != mxConstants.NONE)
+ {
+ // Loads the common language file (no extension)
+ var defaultBundle = mxResources.getDefaultBundle(basename, lan);
+
+ if (defaultBundle != null)
+ {
+ try
+ {
+ var req = mxUtils.load(defaultBundle);
+
+ if (req.isReady())
+ {
+ mxResources.parse(req.getText());
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+
+ // Overlays the language specific file (_lan-extension)
+ var specialBundle = mxResources.getSpecialBundle(basename, lan);
+
+ if (specialBundle != null)
+ {
+ try
+ {
+ var req = mxUtils.load(specialBundle);
+
+ if (req.isReady())
+ {
+ mxResources.parse(req.getText());
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+ }
+ },
+
+ /**
+ * 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)
+ {
+ 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);
+ }
+ }
+
+ value = result.join('');
+ }
+
+ return value;
+ }
+
+};
diff --git a/src/js/util/mxSession.js b/src/js/util/mxSession.js
new file mode 100644
index 0000000..4c2a70c
--- /dev/null
+++ b/src/js/util/mxSession.js
@@ -0,0 +1,674 @@
+/**
+ * $Id: mxSession.js,v 1.46 2012-08-22 15:30:49 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSession
+ *
+ * Session for sharing an <mxGraphModel> with other parties
+ * via a backend that acts as a multicaster for all changes.
+ *
+ * Diagram Sharing:
+ *
+ * The diagram sharing is a mechanism where each atomic change of the model is
+ * encoded into XML using <mxCodec> and then transmitted to the server by the
+ * <mxSession> object. On the server, the XML data is dispatched to each
+ * listener on the same diagram (except the sender), and the XML is decoded
+ * back into atomic changes on the client side, which are then executed on the
+ * model and stored in the command history.
+ *
+ * The <mxSession.significantRemoteChanges> specifies how these changes are
+ * treated with respect to undo: The default value (true) will undo the last
+ * change regardless of whether it was a remote or a local change. If the
+ * switch is false, then an undo will go back until the last local change,
+ * silently undoing all remote changes up to that point. Note that these
+ * changes will be added as new remote changes to the history of the other
+ * clients.
+ *
+ * Event: mxEvent.CONNECT
+ *
+ * Fires after the session has been started, that is, after the response to the
+ * initial request was received and the session goes into polling mode. This
+ * event has no properties.
+ *
+ * Event: mxEvent.SUSPEND
+ *
+ * Fires after <suspend> was called an the session was not already in suspended
+ * state. This event has no properties.
+ *
+ * Event: mxEvent.RESUME
+ *
+ * Fires after the session was resumed in <resume>. This event has no
+ * properties.
+ *
+ * Event: mxEvent.DISCONNECT
+ *
+ * Fires after the session was stopped in <stop>. The <code>reason</code>
+ * property contains the optional exception that was passed to the stop method.
+ *
+ * Event: mxEvent.NOTIFY
+ *
+ * Fires after a notification was sent in <notify>. The <code>url</code>
+ * property contains the URL and the <code>xml</code> property contains the XML
+ * data of the request.
+ *
+ * Event: mxEvent.GET
+ *
+ * Fires after a response was received in <get>. The <code>url</code> property
+ * contains the URL and the <code>request</code> is the <mxXmlRequest> that
+ * contains the response.
+ *
+ * Event: mxEvent.FIRED
+ *
+ * Fires after an array of edits has been executed on the model. The
+ * <code>changes</code> property contains the array of changes.
+ *
+ * Event: mxEvent.RECEIVE
+ *
+ * Fires after an XML node was received in <receive>. The <code>node</code>
+ * property contains the node that was received.
+ *
+ * Constructor: mxSession
+ *
+ * Constructs a new session using the given <mxGraphModel> and URLs to
+ * communicate with the backend.
+ *
+ * Parameters:
+ *
+ * model - <mxGraphModel> that contains the data.
+ * urlInit - URL to be used for initializing the session.
+ * urlPoll - URL to be used for polling the backend.
+ * urlNotify - URL to be used for sending changes to the backend.
+ */
+function mxSession(model, urlInit, urlPoll, urlNotify)
+{
+ this.model = model;
+ this.urlInit = urlInit;
+ this.urlPoll = urlPoll;
+ this.urlNotify = urlNotify;
+
+ // Resolves cells by id using the model
+ if (model != null)
+ {
+ this.codec = new mxCodec();
+
+ this.codec.lookup = function(id)
+ {
+ return model.getCell(id);
+ };
+ }
+
+ // Adds the listener for notifying the backend of any
+ // changes in the model
+ model.addListener(mxEvent.NOTIFY, mxUtils.bind(this, function(sender, evt)
+ {
+ var edit = evt.getProperty('edit');
+
+ if (edit != null && this.debug || (this.connected && !this.suspended))
+ {
+ this.notify('<edit>'+this.encodeChanges(edit.changes, edit.undone)+'</edit>');
+ }
+ }));
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSession.prototype = new mxEventSource();
+mxSession.prototype.constructor = mxSession;
+
+/**
+ * Variable: model
+ *
+ * Reference to the enclosing <mxGraphModel>.
+ */
+mxSession.prototype.model = null;
+
+/**
+ * Variable: urlInit
+ *
+ * URL to initialize the session.
+ */
+mxSession.prototype.urlInit = null;
+
+/**
+ * Variable: urlPoll
+ *
+ * URL for polling the backend.
+ */
+mxSession.prototype.urlPoll = null;
+
+/**
+ * Variable: urlNotify
+ *
+ * URL to send changes to the backend.
+ */
+mxSession.prototype.urlNotify = null;
+
+/**
+ * Variable: codec
+ *
+ * Reference to the <mxCodec> used to encoding and decoding changes.
+ */
+mxSession.prototype.codec = null;
+
+/**
+ * Variable: linefeed
+ *
+ * Used for encoding linefeeds. Default is '&#xa;'.
+ */
+mxSession.prototype.linefeed = '&#xa;';
+
+/**
+ * Variable: escapePostData
+ *
+ * Specifies if the data in the post request sent in <notify>
+ * should be converted using encodeURIComponent. Default is true.
+ */
+mxSession.prototype.escapePostData = true;
+
+/**
+ * Variable: significantRemoteChanges
+ *
+ * Whether remote changes should be significant in the
+ * local command history. Default is true.
+ */
+mxSession.prototype.significantRemoteChanges = true;
+
+/**
+ * Variable: sent
+ *
+ * Total number of sent bytes.
+ */
+mxSession.prototype.sent = 0;
+
+/**
+ * Variable: received
+ *
+ * Total number of received bytes.
+ */
+mxSession.prototype.received = 0;
+
+/**
+ * Variable: debug
+ *
+ * Specifies if the session should run in debug mode. In this mode, no
+ * connection is established. The data is written to the console instead.
+ * Default is false.
+ */
+mxSession.prototype.debug = false;
+
+/**
+ * Variable: connected
+ */
+mxSession.prototype.connected = false;
+
+/**
+ * Variable: send
+ */
+mxSession.prototype.suspended = false;
+
+/**
+ * Variable: polling
+ */
+mxSession.prototype.polling = false;
+
+/**
+ * Function: start
+ */
+mxSession.prototype.start = function()
+{
+ if (this.debug)
+ {
+ this.connected = true;
+ this.fireEvent(new mxEventObject(mxEvent.CONNECT));
+ }
+ else if (!this.connected)
+ {
+ this.get(this.urlInit, mxUtils.bind(this, function(req)
+ {
+ this.connected = true;
+ this.fireEvent(new mxEventObject(mxEvent.CONNECT));
+ this.poll();
+ }));
+ }
+};
+
+/**
+ * Function: suspend
+ *
+ * Suspends the polling. Use <resume> to reactive the session. Fires a
+ * suspend event.
+ */
+mxSession.prototype.suspend = function()
+{
+ if (this.connected && !this.suspended)
+ {
+ this.suspended = true;
+ this.fireEvent(new mxEventObject(mxEvent.SUSPEND));
+ }
+};
+
+/**
+ * Function: resume
+ *
+ * Resumes the session if it has been suspended. Fires a resume-event
+ * before starting the polling.
+ */
+mxSession.prototype.resume = function(type, attr, value)
+{
+ if (this.connected &&
+ this.suspended)
+ {
+ this.suspended = false;
+ this.fireEvent(new mxEventObject(mxEvent.RESUME));
+
+ if (!this.polling)
+ {
+ this.poll();
+ }
+ }
+};
+
+/**
+ * Function: stop
+ *
+ * Stops the session and fires a disconnect event. The given reason is
+ * passed to the disconnect event listener as the second argument.
+ */
+mxSession.prototype.stop = function(reason)
+{
+ if (this.connected)
+ {
+ this.connected = false;
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.DISCONNECT,
+ 'reason', reason));
+};
+
+/**
+ * Function: poll
+ *
+ * Sends an asynchronous GET request to <urlPoll>.
+ */
+mxSession.prototype.poll = function()
+{
+ if (this.connected &&
+ !this.suspended &&
+ this.urlPoll != null)
+ {
+ this.polling = true;
+
+ this.get(this.urlPoll, mxUtils.bind(this, function()
+ {
+ this.poll();
+ }));
+ }
+ else
+ {
+ this.polling = false;
+ }
+};
+
+/**
+ * Function: notify
+ *
+ * Sends out the specified XML to <urlNotify> and fires a <notify> event.
+ */
+mxSession.prototype.notify = function(xml, onLoad, onError)
+{
+ if (xml != null &&
+ xml.length > 0)
+ {
+ if (this.urlNotify != null)
+ {
+ if (this.debug)
+ {
+ mxLog.show();
+ mxLog.debug('mxSession.notify: '+this.urlNotify+' xml='+xml);
+ }
+ else
+ {
+ xml = '<message><delta>'+xml+'</delta></message>';
+
+ if (this.escapePostData)
+ {
+ xml = encodeURIComponent(xml);
+ }
+
+ mxUtils.post(this.urlNotify, 'xml='+xml, onLoad, onError);
+ }
+ }
+
+ this.sent += xml.length;
+ this.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ 'url', this.urlNotify, 'xml', xml));
+ }
+};
+
+/**
+ * Function: get
+ *
+ * Sends an asynchronous get request to the given URL, fires a <get> event
+ * and invokes the given onLoad function when a response is received.
+ */
+mxSession.prototype.get = function(url, onLoad, onError)
+{
+ // Response after browser refresh has no global scope
+ // defined. This response is ignored and the session
+ // stops implicitely.
+ if (typeof(mxUtils) != 'undefined')
+ {
+ var onErrorWrapper = mxUtils.bind(this, function(ex)
+ {
+ if (onError != null)
+ {
+ onError(ex);
+ }
+ else
+ {
+ this.stop(ex);
+ }
+ });
+
+ // Handles a successful response for
+ // the above request.
+ mxUtils.get(url, mxUtils.bind(this, function(req)
+ {
+ if (typeof(mxUtils) != 'undefined')
+ {
+ if (req.isReady() && req.getStatus() != 404)
+ {
+ this.received += req.getText().length;
+ this.fireEvent(new mxEventObject(mxEvent.GET, 'url', url, 'request', req));
+
+ if (this.isValidResponse(req))
+ {
+ if (req.getText().length > 0)
+ {
+ var node = req.getDocumentElement();
+
+ if (node == null)
+ {
+ onErrorWrapper('Invalid response: '+req.getText());
+ }
+ else
+ {
+ this.receive(node);
+ }
+ }
+
+ if (onLoad != null)
+ {
+ onLoad(req);
+ }
+ }
+ }
+ else
+ {
+ onErrorWrapper('Response not ready');
+ }
+ }
+ }),
+ // Handles a transmission error for the
+ // above request
+ function(req)
+ {
+ onErrorWrapper('Transmission error');
+ });
+ }
+};
+
+/**
+ * Function: isValidResponse
+ *
+ * Returns true if the response data in the given <mxXmlRequest> is valid.
+ */
+mxSession.prototype.isValidResponse = function(req)
+{
+ // TODO: Find condition to check if response
+ // contains valid XML (not eg. the PHP code).
+ return req.getText().indexOf('<?php') < 0;
+};
+
+/**
+ * Function: encodeChanges
+ *
+ * Returns the XML representation for the given array of changes.
+ */
+mxSession.prototype.encodeChanges = function(changes, invert)
+{
+ // TODO: Use array for string concatenation
+ var xml = '';
+ var step = (invert) ? -1 : 1;
+ var i0 = (invert) ? changes.length - 1 : 0;
+
+ for (var i = i0; i >= 0 && i < changes.length; i += step)
+ {
+ // Newlines must be kept, they will be converted
+ // to &#xa; when the server sends data to the
+ // client
+ var node = this.codec.encode(changes[i]);
+ xml += mxUtils.getXml(node, this.linefeed);
+ }
+
+ return xml;
+};
+
+/**
+ * Function: receive
+ *
+ * Processes the given node by applying the changes to the model. If the nodename
+ * is state, then the namespace is used as a prefix for creating Ids in the model,
+ * and the child nodes are visited recursively. If the nodename is delta, then the
+ * changes encoded in the child nodes are applied to the model. Each call to the
+ * receive function fires a <receive> event with the given node as the second argument
+ * after processing. If changes are processed, then the function additionally fires
+ * a <mxEvent.FIRED> event before the <mxEvent.RECEIVE> event.
+ */
+mxSession.prototype.receive = function(node)
+{
+ if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Uses the namespace in the model
+ var ns = node.getAttribute('namespace');
+
+ if (ns != null)
+ {
+ this.model.prefix = ns + '-';
+ }
+
+ var child = node.firstChild;
+
+ while (child != null)
+ {
+ var name = child.nodeName.toLowerCase();
+
+ if (name == 'state')
+ {
+ this.processState(child);
+ }
+ else if (name == 'delta')
+ {
+ this.processDelta(child);
+ }
+
+ child = child.nextSibling;
+ }
+
+ // Fires receive event
+ this.fireEvent(new mxEventObject(mxEvent.RECEIVE, 'node', node));
+ }
+};
+
+/**
+ * Function: processState
+ *
+ * Processes the given state node which contains the current state of the
+ * remote model.
+ */
+mxSession.prototype.processState = function(node)
+{
+ var dec = new mxCodec(node.ownerDocument);
+ dec.decode(node.firstChild, this.model);
+};
+
+/**
+ * Function: processDelta
+ *
+ * Processes the given delta node which contains a sequence of edits which in
+ * turn map to one transaction on the remote model each.
+ */
+mxSession.prototype.processDelta = function(node)
+{
+ var edit = node.firstChild;
+
+ while (edit != null)
+ {
+ if (edit.nodeName == 'edit')
+ {
+ this.processEdit(edit);
+ }
+
+ edit = edit.nextSibling;
+ }
+};
+
+/**
+ * Function: processEdit
+ *
+ * Processes the given edit by executing its changes and firing the required
+ * events via the model.
+ */
+mxSession.prototype.processEdit = function(node)
+{
+ var changes = this.decodeChanges(node);
+
+ if (changes.length > 0)
+ {
+ var edit = this.createUndoableEdit(changes);
+
+ // No notify event here to avoid the edit from being encoded and transmitted
+ // LATER: Remove changes property (deprecated)
+ this.model.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ 'edit', edit, 'changes', changes));
+ this.model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ this.fireEvent(new mxEventObject(mxEvent.FIRED, 'edit', edit));
+ }
+};
+
+/**
+ * Function: createUndoableEdit
+ *
+ * Creates a new <mxUndoableEdit> that implements the notify function to fire a
+ * <change> and <notify> event via the model.
+ */
+mxSession.prototype.createUndoableEdit = function(changes)
+{
+ var edit = new mxUndoableEdit(this.model, this.significantRemoteChanges);
+ edit.changes = changes;
+
+ edit.notify = function()
+ {
+ // LATER: Remove changes property (deprecated)
+ edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ 'edit', edit, 'changes', edit.changes));
+ edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ 'edit', edit, 'changes', edit.changes));
+ };
+
+ return edit;
+};
+
+/**
+ * Function: decodeChanges
+ *
+ * Decodes and executes the changes represented by the children in the
+ * given node. Returns an array that contains all changes.
+ */
+mxSession.prototype.decodeChanges = function(node)
+{
+ // Updates the document in the existing codec
+ this.codec.document = node.ownerDocument;
+
+ // Parses and executes the changes on the model
+ var changes = [];
+ node = node.firstChild;
+
+ while (node != null)
+ {
+ var change = this.decodeChange(node);
+
+ if (change != null)
+ {
+ changes.push(change);
+ }
+
+ node = node.nextSibling;
+ }
+
+ return changes;
+};
+
+/**
+ * Function: decodeChange
+ *
+ * Decodes, executes and returns the change object represented by the given
+ * XML node.
+ */
+mxSession.prototype.decodeChange = function(node)
+{
+ var change = null;
+
+ if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ if (node.nodeName == 'mxRootChange')
+ {
+ // Handles the special case were no ids should be
+ // resolved in the existing model. This change will
+ // replace all registered ids and cells from the
+ // model and insert a new cell hierarchy instead.
+ var tmp = new mxCodec(node.ownerDocument);
+ change = tmp.decode(node);
+ }
+ else
+ {
+ change = this.codec.decode(node);
+ }
+
+ if (change != null)
+ {
+ change.model = this.model;
+ change.execute();
+
+ // Workaround for references not being resolved if cells have
+ // been removed from the model prior to being referenced. This
+ // adds removed cells in the codec object lookup table.
+ if (node.nodeName == 'mxChildChange' && change.parent == null)
+ {
+ this.cellRemoved(change.child);
+ }
+ }
+ }
+
+ return change;
+};
+
+/**
+ * Function: cellRemoved
+ *
+ * Adds removed cells to the codec object lookup for references to the removed
+ * cells after this point in time.
+ */
+mxSession.prototype.cellRemoved = function(cell, codec)
+{
+ this.codec.putObject(cell.getId(), cell);
+
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.cellRemoved(this.model.getChildAt(cell, i));
+ }
+};
diff --git a/src/js/util/mxSvgCanvas2D.js b/src/js/util/mxSvgCanvas2D.js
new file mode 100644
index 0000000..4af0642
--- /dev/null
+++ b/src/js/util/mxSvgCanvas2D.js
@@ -0,0 +1,1234 @@
+/**
+ * $Id: mxSvgCanvas2D.js,v 1.18 2012-11-23 15:13:19 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxSvgCanvas2D
+ *
+ * Implements a canvas to be used with <mxImageExport>. 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);
+ * }
+ *
+ * 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)
+ *
+ * Constructor: mxSvgCanvas2D
+ *
+ * Constructs an 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.
+ */
+var mxSvgCanvas2D = function(root, styleEnabled)
+{
+ styleEnabled = (styleEnabled != null) ? styleEnabled : false;
+
+ /**
+ * Variable: converter
+ *
+ * Holds the <mxUrlConverter> to convert image URLs.
+ */
+ var converter = new mxUrlConverter();
+
+ /**
+ * Variable: autoAntiAlias
+ *
+ * Specifies if anti aliasing should be disabled for rectangles
+ * and orthogonal paths. Default is true.
+ */
+ var autoAntiAlias = true;
+
+ /**
+ * Variable: textEnabled
+ *
+ * Specifies if text output should be enabled. Default is true.
+ */
+ var textEnabled = true;
+
+ /**
+ * Variable: foEnabled
+ *
+ * Specifies if use of foreignObject for HTML markup is allowed. Default is true.
+ */
+ var foEnabled = true;
+
+ // Private helper function to create SVG elements
+ var create = function(tagName, namespace)
+ {
+ var doc = root.ownerDocument || document;
+
+ if (doc.createElementNS != null)
+ {
+ return doc.createElementNS(namespace || mxConstants.NS_SVG, tagName);
+ }
+ else
+ {
+ var elt = doc.createElement(tagName);
+
+ if (namespace != null)
+ {
+ elt.setAttribute('xmlns', namespace);
+ }
+
+ return elt;
+ }
+ };
+
+ // Defs section contains optional style and gradients
+ var defs = create('defs');
+
+ // Creates defs section with optional global style
+ if (styleEnabled)
+ {
+ var style = create('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}');
+
+ if (autoAntiAlias)
+ {
+ mxUtils.write(style, 'rect{shape-rendering:crispEdges}');
+ }
+
+ // Appends style to defs and defs to SVG container
+ defs.appendChild(style);
+ }
+
+ root.appendChild(defs);
+
+ // Defines the current state
+ var currentState =
+ {
+ dx: 0,
+ dy: 0,
+ scale: 1,
+ transform: '',
+ fill: null,
+ gradient: null,
+ stroke: null,
+ strokeWidth: 1,
+ dashed: false,
+ dashpattern: '3 3',
+ alpha: 1,
+ linecap: 'flat',
+ linejoin: 'miter',
+ miterlimit: 10,
+ fontColor: '#000000',
+ fontSize: mxConstants.DEFAULT_FONTSIZE,
+ fontFamily: mxConstants.DEFAULT_FONTFAMILY,
+ fontStyle: 0
+ };
+
+ // Local variables
+ var currentPathIsOrthogonal = true;
+ var glassGradient = null;
+ var currentNode = null;
+ var currentPath = null;
+ var lastPoint = null;
+ var gradients = [];
+ var refCount = 0;
+ var stack = [];
+
+ // Other private helper methods
+ var createGradientId = function(start, end, 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();
+ end = end.toLowerCase();
+
+ // 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 start+'-'+end+'-'+dir;
+ };
+
+ var createHtmlBody = function(str, align, valign)
+ {
+ var style = 'margin:0px;font-size:' + Math.floor(currentState.fontSize) + 'px;' +
+ 'font-family:' + currentState.fontFamily + ';color:' + currentState.fontColor+ ';';
+
+ if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ style += 'font-weight:bold;';
+ }
+
+ if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ style += 'font-style:italic;';
+ }
+
+ if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ style += 'font-decoration:underline;';
+ }
+
+ if (align == mxConstants.ALIGN_CENTER)
+ {
+ style += 'text-align:center;';
+ }
+ else if (align == mxConstants.ALIGN_RIGHT)
+ {
+ style += 'text-align:right;';
+ }
+
+ // Converts HTML entities to unicode
+ var t = document.createElement('div');
+ t.innerHTML = str;
+ str = t.innerHTML.replace(/&nbsp;/g, '&#160;');
+
+ // LATER: Add vertical align support via table, adds xmlns to workaround empty NS in IE9 standards
+ var node = mxUtils.parseXml('<div xmlns="http://www.w3.org/1999/xhtml" style="' +
+ style + '">' + str + '</div>').documentElement;
+
+ return node;
+ };
+
+ var getSvgGradient = function(start, end, direction)
+ {
+ var id = createGradientId(start, end, direction);
+ var gradient = gradients[id];
+
+ if (gradient == null)
+ {
+ gradient = create('linearGradient');
+ gradient.setAttribute('id', ++refCount);
+ 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 stop = create('stop');
+ stop.setAttribute('offset', '0%');
+ stop.setAttribute('style', 'stop-color:'+start);
+ gradient.appendChild(stop);
+
+ stop = create('stop');
+ stop.setAttribute('offset', '100%');
+ stop.setAttribute('style', 'stop-color:'+end);
+ gradient.appendChild(stop);
+
+ defs.appendChild(gradient);
+ gradients[id] = gradient;
+ }
+
+ return gradient.getAttribute('id');
+ };
+
+ var appendNode = function(node, state, filled, stroked)
+ {
+ if (node != null)
+ {
+ if (state.clip != null)
+ {
+ node.setAttribute('clip-path', 'url(#' + state.clip + ')');
+ state.clip = null;
+ }
+
+ if (currentPath != null)
+ {
+ node.setAttribute('d', currentPath.join(' '));
+ currentPath = null;
+
+ if (autoAntiAlias && currentPathIsOrthogonal)
+ {
+ node.setAttribute('shape-rendering', 'crispEdges');
+ state.strokeWidth = Math.max(1, state.strokeWidth);
+ }
+ }
+
+ if (state.alpha < 1)
+ {
+ // LATER: Check if using fill/stroke-opacity here is faster
+ node.setAttribute('opacity', state.alpha);
+ //node.setAttribute('fill-opacity', state.alpha);
+ //node.setAttribute('stroke-opacity', state.alpha);
+ }
+
+ if (filled && (state.fill != null || state.gradient != null))
+ {
+ if (state.gradient != null)
+ {
+ node.setAttribute('fill', 'url(#' + state.gradient + ')');
+ }
+ else
+ {
+ node.setAttribute('fill', state.fill.toLowerCase());
+ }
+ }
+ else if (!styleEnabled)
+ {
+ node.setAttribute('fill', 'none');
+ }
+
+ if (stroked && state.stroke != null)
+ {
+ node.setAttribute('stroke', state.stroke.toLowerCase());
+
+ // Sets the stroke properties (1 is default is SVG)
+ if (state.strokeWidth != 1)
+ {
+ if (node.nodeName == 'rect' && autoAntiAlias)
+ {
+ state.strokeWidth = Math.max(1, state.strokeWidth);
+ }
+
+ node.setAttribute('stroke-width', state.strokeWidth);
+ }
+
+ if (node.nodeName == 'path')
+ {
+ // Linejoin miter is default in SVG
+ if (state.linejoin != null && state.linejoin != 'miter')
+ {
+ node.setAttribute('stroke-linejoin', state.linejoin);
+ }
+
+ if (state.linecap != null)
+ {
+ // flat is called butt in SVG
+ var value = state.linecap;
+
+ if (value == 'flat')
+ {
+ value = 'butt';
+ }
+
+ // Linecap butt is default in SVG
+ if (value != 'butt')
+ {
+ node.setAttribute('stroke-linecap', value);
+ }
+ }
+
+ // Miterlimit 10 is default in our document
+ if (state.miterlimit != null && (!styleEnabled || state.miterlimit != 10))
+ {
+ node.setAttribute('stroke-miterlimit', state.miterlimit);
+ }
+ }
+
+ if (state.dashed)
+ {
+ var dash = state.dashpattern.split(' ');
+
+ if (dash.length > 0)
+ {
+ var pat = [];
+
+ for (var i = 0; i < dash.length; i++)
+ {
+ pat[i] = Number(dash[i]) * currentState.strokeWidth;
+ }
+
+
+ node.setAttribute('stroke-dasharray', pat.join(' '));
+ }
+ }
+ }
+
+ if (state.transform.length > 0)
+ {
+ node.setAttribute('transform', state.transform);
+ }
+
+ root.appendChild(node);
+ }
+ };
+
+ // Private helper function to format a number
+ var f2 = function(x)
+ {
+ return Math.round(parseFloat(x) * 100) / 100;
+ };
+
+ // Returns public interface
+ return {
+
+ /**
+ * Function: getConverter
+ *
+ * Returns <converter>.
+ */
+ getConverter: function()
+ {
+ return converter;
+ },
+
+ /**
+ * Function: isAutoAntiAlias
+ *
+ * Returns <autoAntiAlias>.
+ */
+ isAutoAntiAlias: function()
+ {
+ return autoAntiAlias;
+ },
+
+ /**
+ * Function: setAutoAntiAlias
+ *
+ * Sets <autoAntiAlias>.
+ */
+ setAutoAntiAlias: function(value)
+ {
+ autoAntiAlias = value;
+ },
+
+ /**
+ * Function: isTextEnabled
+ *
+ * Returns <textEnabled>.
+ */
+ isTextEnabled: function()
+ {
+ return textEnabled;
+ },
+
+ /**
+ * Function: setTextEnabled
+ *
+ * Sets <textEnabled>.
+ */
+ setTextEnabled: function(value)
+ {
+ textEnabled = value;
+ },
+
+ /**
+ * Function: isFoEnabled
+ *
+ * Returns <foEnabled>.
+ */
+ isFoEnabled: function()
+ {
+ return foEnabled;
+ },
+
+ /**
+ * Function: setFoEnabled
+ *
+ * Sets <foEnabled>.
+ */
+ setFoEnabled: function(value)
+ {
+ foEnabled = value;
+ },
+
+ /**
+ * Function: save
+ *
+ * Saves the state of the graphics object.
+ */
+ save: function()
+ {
+ stack.push(currentState);
+ currentState = mxUtils.clone(currentState);
+ },
+
+ /**
+ * Function: restore
+ *
+ * Restores the state of the graphics object.
+ */
+ restore: function()
+ {
+ currentState = stack.pop();
+ },
+
+ /**
+ * Function: scale
+ *
+ * Scales the current graphics object.
+ */
+ scale: function(value)
+ {
+ currentState.scale *= value;
+ currentState.strokeWidth *= value;
+ },
+
+ /**
+ * Function: translate
+ *
+ * Translates the current graphics object.
+ */
+ translate: function(dx, dy)
+ {
+ currentState.dx += dx;
+ currentState.dy += dy;
+ },
+
+ /**
+ * Function: rotate
+ *
+ * Rotates and/or flips the current graphics object.
+ */
+ rotate: function(theta, flipH, flipV, cx, cy)
+ {
+ cx += currentState.dx;
+ cy += currentState.dy;
+
+ cx *= currentState.scale;
+ cy *= currentState.scale;
+
+ // This implementation uses custom scale/translate and built-in rotation
+ // Rotation state is part of the AffineTransform in state.transform
+ if (flipH ^ flipV)
+ {
+ var tx = (flipH) ? cx : 0;
+ var sx = (flipH) ? -1 : 1;
+
+ var ty = (flipV) ? cy : 0;
+ var sy = (flipV) ? -1 : 1;
+
+ currentState.transform += 'translate(' + f2(tx) + ',' + f2(ty) + ')';
+ currentState.transform += 'scale(' + f2(sx) + ',' + f2(sy) + ')';
+ currentState.transform += 'translate(' + f2(-tx) + ' ' + f2(-ty) + ')';
+ }
+
+ currentState.transform += 'rotate(' + f2(theta) + ',' + f2(cx) + ',' + f2(cy) + ')';
+ },
+
+ /**
+ * Function: setStrokeWidth
+ *
+ * Sets the stroke width.
+ */
+ setStrokeWidth: function(value)
+ {
+ currentState.strokeWidth = value * currentState.scale;
+ },
+
+ /**
+ * Function: setStrokeColor
+ *
+ * Sets the stroke color.
+ */
+ setStrokeColor: function(value)
+ {
+ currentState.stroke = value;
+ },
+
+ /**
+ * Function: setDashed
+ *
+ * Sets the dashed state to true or false.
+ */
+ setDashed: function(value)
+ {
+ currentState.dashed = value;
+ },
+
+ /**
+ * Function: setDashPattern
+ *
+ * Sets the dashed pattern to the given space separated list of numbers.
+ */
+ setDashPattern: function(value)
+ {
+ currentState.dashpattern = value;
+ },
+
+ /**
+ * Function: setLineCap
+ *
+ * Sets the linecap.
+ */
+ setLineCap: function(value)
+ {
+ currentState.linecap = value;
+ },
+
+ /**
+ * Function: setLineJoin
+ *
+ * Sets the linejoin.
+ */
+ setLineJoin: function(value)
+ {
+ currentState.linejoin = value;
+ },
+
+ /**
+ * Function: setMiterLimit
+ *
+ * Sets the miterlimit.
+ */
+ setMiterLimit: function(value)
+ {
+ currentState.miterlimit = value;
+ },
+
+ /**
+ * Function: setFontSize
+ *
+ * Sets the fontsize.
+ */
+ setFontSize: function(value)
+ {
+ currentState.fontSize = value;
+ },
+
+ /**
+ * Function: setFontColor
+ *
+ * Sets the fontcolor.
+ */
+ setFontColor: function(value)
+ {
+ currentState.fontColor = value;
+ },
+
+ /**
+ * Function: setFontFamily
+ *
+ * Sets the fontfamily.
+ */
+ setFontFamily: function(value)
+ {
+ currentState.fontFamily = value;
+ },
+
+ /**
+ * Function: setFontStyle
+ *
+ * Sets the fontstyle.
+ */
+ setFontStyle: function(value)
+ {
+ currentState.fontStyle = value;
+ },
+
+ /**
+ * Function: setAlpha
+ *
+ * Sets the current alpha.
+ */
+ setAlpha: function(alpha)
+ {
+ currentState.alpha = alpha;
+ },
+
+ /**
+ * Function: setFillColor
+ *
+ * Sets the fillcolor.
+ */
+ setFillColor: function(value)
+ {
+ currentState.fill = value;
+ currentState.gradient = null;
+ },
+
+ /**
+ * Function: setGradient
+ *
+ * Sets the gradient color.
+ */
+ setGradient: function(color1, color2, x, y, w, h, direction)
+ {
+ if (color1 != null && color2 != null)
+ {
+ currentState.gradient = getSvgGradient(color1, color2, direction);
+ currentState.fill = color1;
+ }
+ },
+
+ /**
+ * Function: setGlassGradient
+ *
+ * Sets the glass gradient.
+ */
+ setGlassGradient: function(x, y, w, h)
+ {
+ // Creates glass overlay gradient
+ if (glassGradient == null)
+ {
+ glassGradient = create('linearGradient');
+ glassGradient.setAttribute('id', '0');
+ glassGradient.setAttribute('x1', '0%');
+ glassGradient.setAttribute('y1', '0%');
+ glassGradient.setAttribute('x2', '0%');
+ glassGradient.setAttribute('y2', '100%');
+
+ var stop1 = create('stop');
+ stop1.setAttribute('offset', '0%');
+ stop1.setAttribute('style', 'stop-color:#ffffff;stop-opacity:0.9');
+ glassGradient.appendChild(stop1);
+
+ var stop2 = create('stop');
+ stop2.setAttribute('offset', '100%');
+ stop2.setAttribute('style', 'stop-color:#ffffff;stop-opacity:0.1');
+ glassGradient.appendChild(stop2);
+
+ // Makes it the first entry of all gradients in defs
+ if (defs.firstChild.nextSibling != null)
+ {
+ defs.insertBefore(glassGradient, defs.firstChild.nextSibling);
+ }
+ else
+ {
+ defs.appendChild(glassGradient);
+ }
+ }
+
+ // Glass gradient has hardcoded ID (see above)
+ currentState.gradient = '0';
+ },
+
+ /**
+ * Function: rect
+ *
+ * Sets the current path to a rectangle.
+ */
+ rect: function(x, y, w, h)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+
+ currentNode = create('rect');
+ currentNode.setAttribute('x', f2(x * currentState.scale));
+ currentNode.setAttribute('y', f2(y * currentState.scale));
+ currentNode.setAttribute('width', f2(w * currentState.scale));
+ currentNode.setAttribute('height', f2(h * currentState.scale));
+
+ if (!styleEnabled && autoAntiAlias)
+ {
+ currentNode.setAttribute('shape-rendering', 'crispEdges');
+ }
+ },
+
+ /**
+ * Function: roundrect
+ *
+ * Sets the current path to a rounded rectangle.
+ */
+ roundrect: function(x, y, w, h, dx, dy)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+
+ currentNode = create('rect');
+ currentNode.setAttribute('x', f2(x * currentState.scale));
+ currentNode.setAttribute('y', f2(y * currentState.scale));
+ currentNode.setAttribute('width', f2(w * currentState.scale));
+ currentNode.setAttribute('height', f2(h * currentState.scale));
+
+ if (dx > 0)
+ {
+ currentNode.setAttribute('rx', f2(dx * currentState.scale));
+ }
+
+ if (dy > 0)
+ {
+ currentNode.setAttribute('ry', f2(dy * currentState.scale));
+ }
+
+ if (!styleEnabled && autoAntiAlias)
+ {
+ currentNode.setAttribute('shape-rendering', 'crispEdges');
+ }
+ },
+
+ /**
+ * Function: ellipse
+ *
+ * Sets the current path to an ellipse.
+ */
+ ellipse: function(x, y, w, h)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+
+ currentNode = create('ellipse');
+ currentNode.setAttribute('cx', f2((x + w / 2) * currentState.scale));
+ currentNode.setAttribute('cy', f2((y + h / 2) * currentState.scale));
+ currentNode.setAttribute('rx', f2(w / 2 * currentState.scale));
+ currentNode.setAttribute('ry', f2(h / 2 * currentState.scale));
+ },
+
+ /**
+ * Function: image
+ *
+ * Paints an image.
+ */
+ image: function(x, y, w, h, src, aspect, flipH, flipV)
+ {
+ src = converter.convert(src);
+
+ // TODO: Add option for embedded images as base64. Current
+ // known issues are binary loading of cross-domain images.
+ aspect = (aspect != null) ? aspect : true;
+ flipH = (flipH != null) ? flipH : false;
+ flipV = (flipV != null) ? flipV : false;
+ x += currentState.dx;
+ y += currentState.dy;
+
+ var node = create('image');
+ node.setAttribute('x', f2(x * currentState.scale));
+ node.setAttribute('y', f2(y * currentState.scale));
+ node.setAttribute('width', f2(w * currentState.scale));
+ node.setAttribute('height', f2(h * currentState.scale));
+
+ if (mxClient.IS_VML)
+ {
+ node.setAttribute('xlink:href', src);
+ }
+ else
+ {
+ node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
+ }
+
+ if (!aspect)
+ {
+ node.setAttribute('preserveAspectRatio', 'none');
+ }
+
+ if (currentState.alpha < 1)
+ {
+ node.setAttribute('opacity', currentState.alpha);
+ }
+
+
+ var tr = currentState.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 transforms
+ tr += 'scale(' + sx + ',' + sy + ')translate(' + dx + ',' + dy + ')';
+ }
+
+ if (tr.length > 0)
+ {
+ node.setAttribute('transform', tr);
+ }
+
+ root.appendChild(node);
+ },
+
+ /**
+ * Function: text
+ *
+ * Paints the given text. Possible values for format are empty string for
+ * plain text and html for HTML markup.
+ */
+ text: function(x, y, w, h, str, align, valign, vertical, wrap, format)
+ {
+ if (textEnabled)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+
+ if (foEnabled && format == 'html')
+ {
+ var node = create('g');
+ node.setAttribute('transform', currentState.transform + 'scale(' + currentState.scale + ',' + currentState.scale + ')');
+
+ if (currentState.alpha < 1)
+ {
+ node.setAttribute('opacity', currentState.alpha);
+ }
+
+ var fo = create('foreignObject');
+ fo.setAttribute('x', Math.round(x));
+ fo.setAttribute('y', Math.round(y));
+ fo.setAttribute('width', Math.round(w));
+ fo.setAttribute('height', Math.round(h));
+ fo.appendChild(createHtmlBody(str, align, valign));
+ node.appendChild(fo);
+ root.appendChild(node);
+ }
+ else
+ {
+ var size = Math.floor(currentState.fontSize);
+ var node = create('g');
+ var tr = currentState.transform;
+
+ if (vertical)
+ {
+ var cx = x + w / 2;
+ var cy = y + h / 2;
+ tr += 'rotate(-90,' + f2(cx * currentState.scale) + ',' + f2(cy * currentState.scale) + ')';
+ }
+
+ if (tr.length > 0)
+ {
+ node.setAttribute('transform', tr);
+ }
+
+ if (currentState.alpha < 1)
+ {
+ node.setAttribute('opacity', currentState.alpha);
+ }
+
+ // Default is left
+ var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' :
+ (align == mxConstants.ALIGN_CENTER) ? 'middle' :
+ 'start';
+
+ if (anchor == 'end')
+ {
+ x += Math.max(0, w - 2);
+ }
+ else if (anchor == 'middle')
+ {
+ x += w / 2;
+ }
+ else
+ {
+ x += (w > 0) ? 2 : 0;
+ }
+
+ if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ node.setAttribute('font-weight', 'bold');
+ }
+
+ if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ node.setAttribute('font-style', 'italic');
+ }
+
+ if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ node.setAttribute('text-decoration', 'underline');
+ }
+
+ // Text-anchor start is default in SVG
+ if (anchor != 'start')
+ {
+ node.setAttribute('text-anchor', anchor);
+ }
+
+ if (!styleEnabled || size != mxConstants.DEFAULT_FONTSIZE)
+ {
+ node.setAttribute('font-size', Math.floor(size * currentState.scale) + 'px');
+ }
+
+ if (!styleEnabled || currentState.fontFamily != mxConstants.DEFAULT_FONTFAMILY)
+ {
+ node.setAttribute('font-family', currentState.fontFamily);
+ }
+
+ node.setAttribute('fill', currentState.fontColor);
+
+ var lines = str.split('\n');
+
+ var lineHeight = size * 1.25;
+ var textHeight = (h > 0) ? size + (lines.length - 1) * lineHeight : lines.length * lineHeight - 1;
+ var dy = h - textHeight;
+
+ // Top is default
+ if (valign == null || valign == mxConstants.ALIGN_TOP)
+ {
+ y = Math.max(y - 3 * currentState.scale, y + dy / 2 + ((h > 0) ? lineHeight / 2 - 8 : 0));
+ }
+ else if (valign == mxConstants.ALIGN_MIDDLE)
+ {
+ y = y + dy / 2;
+ }
+ else if (valign == mxConstants.ALIGN_BOTTOM)
+ {
+ y = Math.min(y, y + dy + 2 * currentState.scale);
+ }
+
+ y += size;
+
+ for (var i = 0; i < lines.length; i++)
+ {
+ var text = create('text');
+ text.setAttribute('x', f2(x * currentState.scale));
+ text.setAttribute('y', f2(y * currentState.scale));
+
+ mxUtils.write(text, lines[i]);
+ node.appendChild(text);
+ y += size * 1.3;
+ }
+
+ root.appendChild(node);
+ }
+ }
+ },
+
+ /**
+ * Function: begin
+ *
+ * Starts a new path.
+ */
+ begin: function()
+ {
+ currentNode = create('path');
+ currentPath = [];
+ lastPoint = null;
+ currentPathIsOrthogonal = true;
+ },
+
+ /**
+ * Function: moveTo
+ *
+ * Moves the current path the given coordinates.
+ */
+ moveTo: function(x, y)
+ {
+ if (currentPath != null)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+ currentPath.push('M ' + f2(x * currentState.scale) + ' ' + f2(y * currentState.scale));
+
+ if (autoAntiAlias)
+ {
+ lastPoint = new mxPoint(x, y);
+ }
+ }
+ },
+
+ /**
+ * Function: lineTo
+ *
+ * Adds a line to the current path.
+ */
+ lineTo: function(x, y)
+ {
+ if (currentPath != null)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+ currentPath.push('L ' + f2(x * currentState.scale) + ' ' + f2(y * currentState.scale));
+
+ if (autoAntiAlias)
+ {
+ if (lastPoint != null && currentPathIsOrthogonal && x != lastPoint.x && y != lastPoint.y)
+ {
+ currentPathIsOrthogonal = false;
+ }
+
+ lastPoint = new mxPoint(x, y);
+ }
+ }
+ },
+
+ /**
+ * Function: quadTo
+ *
+ * Adds a quadratic curve to the current path.
+ */
+ quadTo: function(x1, y1, x2, y2)
+ {
+ if (currentPath != null)
+ {
+ x1 += currentState.dx;
+ y1 += currentState.dy;
+ x2 += currentState.dx;
+ y2 += currentState.dy;
+ currentPath.push('Q ' + f2(x1 * currentState.scale) + ' ' + f2(y1 * currentState.scale) +
+ ' ' + f2(x2 * currentState.scale) + ' ' + f2(y2 * currentState.scale));
+ currentPathIsOrthogonal = false;
+ }
+ },
+
+ /**
+ * Function: curveTo
+ *
+ * Adds a bezier curve to the current path.
+ */
+ curveTo: function(x1, y1, x2, y2, x3, y3)
+ {
+ if (currentPath != null)
+ {
+ x1 += currentState.dx;
+ y1 += currentState.dy;
+ x2 += currentState.dx;
+ y2 += currentState.dy;
+ x3 += currentState.dx;
+ y3 += currentState.dy;
+ currentPath.push('C ' + f2(x1 * currentState.scale) + ' ' + f2(y1 * currentState.scale) +
+ ' ' + f2(x2 * currentState.scale) + ' ' + f2(y2 * currentState.scale) +' ' +
+ f2(x3 * currentState.scale) + ' ' + f2(y3 * currentState.scale));
+ currentPathIsOrthogonal = false;
+ }
+ },
+
+ /**
+ * Function: close
+ *
+ * Closes the current path.
+ */
+ close: function()
+ {
+ if (currentPath != null)
+ {
+ currentPath.push('Z');
+ }
+ },
+
+ /**
+ * Function: stroke
+ *
+ * Paints the outline of the current path.
+ */
+ stroke: function()
+ {
+ appendNode(currentNode, currentState, false, true);
+ },
+
+ /**
+ * Function: fill
+ *
+ * Fills the current path.
+ */
+ fill: function()
+ {
+ appendNode(currentNode, currentState, true, false);
+ },
+
+ /**
+ * Function: fillstroke
+ *
+ * Fills and paints the outline of the current path.
+ */
+ fillAndStroke: function()
+ {
+ appendNode(currentNode, currentState, true, true);
+ },
+
+ /**
+ * Function: shadow
+ *
+ * Paints the current path as a shadow of the given color.
+ */
+ shadow: function(value, filled)
+ {
+ this.save();
+ this.setStrokeColor(value);
+
+ if (filled)
+ {
+ this.setFillColor(value);
+ this.fillAndStroke();
+ }
+ else
+ {
+ this.stroke();
+ }
+
+ this.restore();
+ },
+
+ /**
+ * Function: clip
+ *
+ * Uses the current path for clipping.
+ */
+ clip: function()
+ {
+ if (currentNode != null)
+ {
+ if (currentPath != null)
+ {
+ currentNode.setAttribute('d', currentPath.join(' '));
+ currentPath = null;
+ }
+
+ var id = ++refCount;
+ var clip = create('clipPath');
+ clip.setAttribute('id', id);
+ clip.appendChild(currentNode);
+ defs.appendChild(clip);
+ currentState.clip = id;
+ }
+ }
+ };
+
+}; \ No newline at end of file
diff --git a/src/js/util/mxToolbar.js b/src/js/util/mxToolbar.js
new file mode 100644
index 0000000..754e6b3
--- /dev/null
+++ b/src/js/util/mxToolbar.js
@@ -0,0 +1,528 @@
+/**
+ * $Id: mxToolbar.js,v 1.36 2012-06-22 11:17:13 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxToolbar
+ *
+ * Creates a toolbar inside a given DOM node. The toolbar may contain icons,
+ * buttons and combo boxes.
+ *
+ * Event: mxEvent.SELECT
+ *
+ * Fires when an item was selected in the toolbar. The <code>function</code>
+ * property contains the function that was selected in <selectMode>.
+ *
+ * Constructor: mxToolbar
+ *
+ * Constructs a toolbar in the specified container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+function mxToolbar(container)
+{
+ this.container = container;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxToolbar.prototype = new mxEventSource();
+mxToolbar.prototype.constructor = mxToolbar;
+
+/**
+ * Variable: container
+ *
+ * Reference to the DOM nodes that contains the toolbar.
+ */
+mxToolbar.prototype.container = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxToolbar.prototype.enabled = true;
+
+/**
+ * Variable: noReset
+ *
+ * Specifies if <resetMode> requires a forced flag of true for resetting
+ * the current mode in the toolbar. Default is false. This is set to true
+ * if the toolbar item is double clicked to avoid a reset after a single
+ * use of the item.
+ */
+mxToolbar.prototype.noReset = false;
+
+/**
+ * Variable: updateDefaultMode
+ *
+ * Boolean indicating if the default mode should be the last selected
+ * switch mode or the first inserted switch mode. Default is true, that
+ * is the last selected switch mode is the default mode. The default mode
+ * is the mode to be selected after a reset of the toolbar. If this is
+ * false, then the default mode is the first inserted mode item regardless
+ * of what was last selected. Otherwise, the selected item after a reset is
+ * the previously selected item.
+ */
+mxToolbar.prototype.updateDefaultMode = true;
+
+/**
+ * Function: addItem
+ *
+ * Adds the given function as an image with the specified title and icon
+ * and returns the new image node.
+ *
+ * Parameters:
+ *
+ * title - Optional string that is used as the tooltip.
+ * icon - Optional URL of the image to be used. If no URL is given, then a
+ * button is created.
+ * funct - Function to execute on a mouse click.
+ * pressedIcon - Optional URL of the pressed image. Default is a gray
+ * background.
+ * style - Optional style classname. Default is mxToolbarItem.
+ * factoryMethod - Optional factory method for popup menu, eg.
+ * function(menu, evt, cell) { menu.addItem('Hello, World!'); }
+ */
+mxToolbar.prototype.addItem = function(title, icon, funct, pressedIcon, style, factoryMethod)
+{
+ var img = document.createElement((icon != null) ? 'img' : 'button');
+ var initialClassName = style || ((factoryMethod != null) ?
+ 'mxToolbarMode' : 'mxToolbarItem');
+ img.className = initialClassName;
+ img.setAttribute('src', icon);
+
+ if (title != null)
+ {
+ if (icon != null)
+ {
+ img.setAttribute('title', title);
+ }
+ else
+ {
+ mxUtils.write(img, title);
+ }
+ }
+
+ this.container.appendChild(img);
+
+ // Invokes the function on a click on the toolbar item
+ if (funct != null)
+ {
+ mxEvent.addListener(img, (mxClient.IS_TOUCH) ? 'touchend' : 'click', funct);
+ }
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ // Highlights the toolbar item with a gray background
+ // while it is being clicked with the mouse
+ mxEvent.addListener(img, md, 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;
+ };
+ }
+ }
+ }
+ }));
+
+ var mouseHandler = mxUtils.bind(this, function(evt)
+ {
+ if (pressedIcon != null)
+ {
+ img.setAttribute('src', icon);
+ }
+ else
+ {
+ img.style.backgroundColor = '';
+ }
+ });
+
+ mxEvent.addListener(img, mu, 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();
+ }
+};
diff --git a/src/js/util/mxUndoManager.js b/src/js/util/mxUndoManager.js
new file mode 100644
index 0000000..2cb93cb
--- /dev/null
+++ b/src/js/util/mxUndoManager.js
@@ -0,0 +1,229 @@
+/**
+ * $Id: mxUndoManager.js,v 1.30 2011-10-05 06:39:19 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxUndoManager
+ *
+ * Implements a command history. When changing the graph model, an
+ * <mxUndoableChange> object is created at the start of the transaction (when
+ * model.beginUpdate is called). All atomic changes are then added to this
+ * object until the last model.endUpdate call, at which point the
+ * <mxUndoableEdit> is dispatched in an event, and added to the history inside
+ * <mxUndoManager>. This is done by an event listener in
+ * <mxEditor.installUndoHandler>.
+ *
+ * Each atomic change of the model is represented by an object (eg.
+ * <mxRootChange>, <mxChildChange>, <mxTerminalChange> etc) which contains the
+ * complete undo information. The <mxUndoManager> also listens to the
+ * <mxGraphView> and stores it's changes to the current root as insignificant
+ * undoable changes, so that drilling (step into, step up) is undone.
+ *
+ * This means when you execute an atomic change on the model, then change the
+ * current root on the view and click undo, the change of the root will be
+ * undone together with the change of the model so that the display represents
+ * the state at which the model was changed. However, these changes are not
+ * transmitted for sharing as they do not represent a state change.
+ *
+ * Example:
+ *
+ * When adding an undo manager to a graph, make sure to add it
+ * to the model and the view as well to maintain a consistent
+ * display across multiple undo/redo steps.
+ *
+ * (code)
+ * var undoManager = new mxUndoManager();
+ * var listener = function(sender, evt)
+ * {
+ * undoManager.undoableEditHappened(evt.getProperty('edit'));
+ * };
+ * graph.getModel().addListener(mxEvent.UNDO, listener);
+ * graph.getView().addListener(mxEvent.UNDO, listener);
+ * (end)
+ *
+ * The code creates a function that informs the undoManager
+ * of an undoable edit and binds it to the undo event of
+ * <mxGraphModel> and <mxGraphView> using
+ * <mxEventSource.addListener>.
+ *
+ * Event: mxEvent.CLEAR
+ *
+ * Fires after <clear> was invoked. This event has no properties.
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires afer a significant edit was undone in <undo>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was undone.
+ *
+ * Event: mxEvent.REDO
+ *
+ * Fires afer a significant edit was redone in <redo>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was redone.
+ *
+ * Event: mxEvent.ADD
+ *
+ * Fires after an undoable edit was added to the history. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was added.
+ *
+ * Constructor: mxUndoManager
+ *
+ * Constructs a new undo manager with the given history size. If no history
+ * size is given, then a default size of 100 steps is used.
+ */
+function mxUndoManager(size)
+{
+ this.size = (size != null) ? size : 100;
+ this.clear();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUndoManager.prototype = new mxEventSource();
+mxUndoManager.prototype.constructor = mxUndoManager;
+
+/**
+ * Variable: size
+ *
+ * Maximum command history size. 0 means unlimited history. Default is
+ * 100.
+ */
+mxUndoManager.prototype.size = null;
+
+/**
+ * Variable: history
+ *
+ * Array that contains the steps of the command history.
+ */
+mxUndoManager.prototype.history = null;
+
+/**
+ * Variable: indexOfNextAdd
+ *
+ * Index of the element to be added next.
+ */
+mxUndoManager.prototype.indexOfNextAdd = 0;
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if the history is empty.
+ */
+mxUndoManager.prototype.isEmpty = function()
+{
+ return this.history.length == 0;
+};
+
+/**
+ * Function: clear
+ *
+ * Clears the command history.
+ */
+mxUndoManager.prototype.clear = function()
+{
+ this.history = [];
+ this.indexOfNextAdd = 0;
+ this.fireEvent(new mxEventObject(mxEvent.CLEAR));
+};
+
+/**
+ * Function: canUndo
+ *
+ * Returns true if an undo is possible.
+ */
+mxUndoManager.prototype.canUndo = function()
+{
+ return this.indexOfNextAdd > 0;
+};
+
+/**
+ * Function: undo
+ *
+ * Undoes the last change.
+ */
+mxUndoManager.prototype.undo = function()
+{
+ while (this.indexOfNextAdd > 0)
+ {
+ var edit = this.history[--this.indexOfNextAdd];
+ edit.undo();
+
+ if (edit.isSignificant())
+ {
+ this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ break;
+ }
+ }
+};
+
+/**
+ * Function: canRedo
+ *
+ * Returns true if a redo is possible.
+ */
+mxUndoManager.prototype.canRedo = function()
+{
+ return this.indexOfNextAdd < this.history.length;
+};
+
+/**
+ * Function: redo
+ *
+ * Redoes the last change.
+ */
+mxUndoManager.prototype.redo = function()
+{
+ var n = this.history.length;
+
+ while (this.indexOfNextAdd < n)
+ {
+ var edit = this.history[this.indexOfNextAdd++];
+ edit.redo();
+
+ if (edit.isSignificant())
+ {
+ this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));
+ break;
+ }
+ }
+};
+
+/**
+ * Function: undoableEditHappened
+ *
+ * Method to be called to add new undoable edits to the <history>.
+ */
+mxUndoManager.prototype.undoableEditHappened = function(undoableEdit)
+{
+ this.trim();
+
+ if (this.size > 0 &&
+ this.size == this.history.length)
+ {
+ this.history.shift();
+ }
+
+ this.history.push(undoableEdit);
+ this.indexOfNextAdd = this.history.length;
+ this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));
+};
+
+/**
+ * Function: trim
+ *
+ * Removes all pending steps after <indexOfNextAdd> from the history,
+ * invoking die on each edit. This is called from <undoableEditHappened>.
+ */
+mxUndoManager.prototype.trim = function()
+{
+ if (this.history.length > this.indexOfNextAdd)
+ {
+ var edits = this.history.splice(this.indexOfNextAdd,
+ this.history.length - this.indexOfNextAdd);
+
+ for (var i = 0; i < edits.length; i++)
+ {
+ edits[i].die();
+ }
+ }
+};
diff --git a/src/js/util/mxUndoableEdit.js b/src/js/util/mxUndoableEdit.js
new file mode 100644
index 0000000..886c262
--- /dev/null
+++ b/src/js/util/mxUndoableEdit.js
@@ -0,0 +1,168 @@
+/**
+ * $Id: mxUndoableEdit.js,v 1.14 2010-09-15 16:58:51 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxUndoableEdit
+ *
+ * Implements a composite undoable edit.
+ *
+ * Constructor: mxUndoableEdit
+ *
+ * Constructs a new undoable edit for the given source.
+ */
+function mxUndoableEdit(source, significant)
+{
+ this.source = source;
+ this.changes = [];
+ this.significant = (significant != null) ? significant : true;
+};
+
+/**
+ * Variable: source
+ *
+ * Specifies the source of the edit.
+ */
+mxUndoableEdit.prototype.source = null;
+
+/**
+ * Variable: changes
+ *
+ * Array that contains the changes that make up this edit. The changes are
+ * expected to either have an undo and redo function, or an execute
+ * function. Default is an empty array.
+ */
+mxUndoableEdit.prototype.changes = null;
+
+/**
+ * Variable: significant
+ *
+ * Specifies if the undoable change is significant.
+ * Default is true.
+ */
+mxUndoableEdit.prototype.significant = null;
+
+/**
+ * Variable: undone
+ *
+ * Specifies if this edit has been undone. Default is false.
+ */
+mxUndoableEdit.prototype.undone = false;
+
+/**
+ * Variable: redone
+ *
+ * Specifies if this edit has been redone. Default is false.
+ */
+mxUndoableEdit.prototype.redone = false;
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if the this edit contains no changes.
+ */
+mxUndoableEdit.prototype.isEmpty = function()
+{
+ return this.changes.length == 0;
+};
+
+/**
+ * Function: isSignificant
+ *
+ * Returns <significant>.
+ */
+mxUndoableEdit.prototype.isSignificant = function()
+{
+ return this.significant;
+};
+
+/**
+ * Function: add
+ *
+ * Adds the specified change to this edit. The change is an object that is
+ * expected to either have an undo and redo, or an execute function.
+ */
+mxUndoableEdit.prototype.add = function(change)
+{
+ this.changes.push(change);
+};
+
+/**
+ * Function: notify
+ *
+ * Hook to notify any listeners of the changes after an <undo> or <redo>
+ * has been carried out. This implementation is empty.
+ */
+mxUndoableEdit.prototype.notify = function() { };
+
+/**
+ * Function: die
+ *
+ * Hook to free resources after the edit has been removed from the command
+ * history. This implementation is empty.
+ */
+mxUndoableEdit.prototype.die = function() { };
+
+/**
+ * Function: undo
+ *
+ * Undoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.undo = function()
+{
+ if (!this.undone)
+ {
+ 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();
+ }
+ }
+
+ this.undone = true;
+ this.redone = false;
+ }
+
+ this.notify();
+};
+
+/**
+ * Function: redo
+ *
+ * Redoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.redo = function()
+{
+ if (!this.redone)
+ {
+ 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();
+ }
+ }
+
+ this.undone = false;
+ this.redone = true;
+ }
+
+ this.notify();
+};
diff --git a/src/js/util/mxUrlConverter.js b/src/js/util/mxUrlConverter.js
new file mode 100644
index 0000000..764767f
--- /dev/null
+++ b/src/js/util/mxUrlConverter.js
@@ -0,0 +1,141 @@
+/**
+ * $Id: mxUrlConverter.js,v 1.3 2012-08-24 17:10:41 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxUrlConverter
+ *
+ * Converts relative and absolute URLs to absolute URLs with protocol and domain.
+ */
+var mxUrlConverter = function(root)
+{
+ /**
+ * Variable: enabled
+ *
+ * Specifies if the converter is enabled. Default is true.
+ */
+ var enabled = true;
+
+ /**
+ * Variable: baseUrl
+ *
+ * Specifies the base URL to be used as a prefix for relative URLs.
+ */
+ var baseUrl = null;
+
+ /**
+ * Variable: baseDomain
+ *
+ * Specifies the base domain to be used as a prefix for absolute URLs.
+ */
+ var baseDomain = null;
+
+ // Private helper function to update the base URL
+ var updateBaseUrl = function()
+ {
+ baseDomain = location.protocol + '//' + location.host;
+ baseUrl = baseDomain + location.pathname;
+ var tmp = baseUrl.lastIndexOf('/');
+
+ // Strips filename etc
+ if (tmp > 0)
+ {
+ baseUrl = baseUrl.substring(0, tmp + 1);
+ }
+ };
+
+ // Returns public interface
+ return {
+
+ /**
+ * Function: isEnabled
+ *
+ * Returns <enabled>.
+ */
+ isEnabled: function()
+ {
+ return enabled;
+ },
+
+ /**
+ * Function: setEnabled
+ *
+ * Sets <enabled>.
+ */
+ setEnabled: function(value)
+ {
+ enabled = value;
+ },
+
+ /**
+ * Function: getBaseUrl
+ *
+ * Returns <baseUrl>.
+ */
+ getBaseUrl: function()
+ {
+ return baseUrl;
+ },
+
+ /**
+ * Function: setBaseUrl
+ *
+ * Sets <baseUrl>.
+ */
+ setBaseUrl: function(value)
+ {
+ baseUrl = value;
+ },
+
+ /**
+ * Function: getBaseDomain
+ *
+ * Returns <baseDomain>.
+ */
+ getBaseDomain: function()
+ {
+ return baseUrl;
+ },
+
+ /**
+ * Function: setBaseDomain
+ *
+ * Sets <baseDomain>.
+ */
+ setBaseDomain: function(value)
+ {
+ baseUrl = value;
+ },
+
+ /**
+ * Function: convert
+ *
+ * Converts the given URL to an absolute URL with protol and domain.
+ * Relative URLs are first converted to absolute URLs.
+ */
+ convert: function(url)
+ {
+ if (enabled && url.indexOf('http://') != 0 && url.indexOf('https://') != 0 && url.indexOf('data:image') != 0)
+ {
+ if (baseUrl == null)
+ {
+ updateBaseUrl();
+ }
+
+ if (url.charAt(0) == '/')
+ {
+ url = baseDomain + url;
+ }
+ else
+ {
+ url = baseUrl + url;
+ }
+ }
+
+ return url;
+ }
+
+ };
+
+}; \ No newline at end of file
diff --git a/src/js/util/mxUtils.js b/src/js/util/mxUtils.js
new file mode 100644
index 0000000..34c0318
--- /dev/null
+++ b/src/js/util/mxUtils.js
@@ -0,0 +1,3920 @@
+/**
+ * $Id: mxUtils.js,v 1.297 2012-12-07 19:47:29 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxUtils =
+{
+ /**
+ * Class: mxUtils
+ *
+ * A singleton class that provides cross-browser helper methods.
+ * This is a global functionality. To access the functions in this
+ * class, use the global classname appended by the functionname.
+ * You may have to load chrome://global/content/contentAreaUtils.js
+ * to disable certain security restrictions in Mozilla for the <open>,
+ * <save>, <saveAs> and <copy> function.
+ *
+ * For example, the following code displays an error message:
+ *
+ * (code)
+ * mxUtils.error('Browser is not supported!', 200, false);
+ * (end)
+ *
+ * Variable: errorResource
+ *
+ * Specifies the resource key for the title of the error window. If the
+ * resource for this key does not exist then the value is used as
+ * the title. Default is 'error'.
+ */
+ errorResource: (mxClient.language != 'none') ? 'error' : '',
+
+ /**
+ * Variable: closeResource
+ *
+ * Specifies the resource key for the label of the close button. If the
+ * resource for this key does not exist then the value is used as
+ * the label. Default is 'close'.
+ */
+ closeResource: (mxClient.language != 'none') ? 'close' : '',
+
+ /**
+ * Variable: errorImage
+ *
+ * Defines the image used for error dialogs.
+ */
+ errorImage: mxClient.imageBasePath + '/error.gif',
+
+ /**
+ * Function: removeCursors
+ *
+ * Removes the cursors from the style of the given DOM node and its
+ * descendants.
+ *
+ * Parameters:
+ *
+ * element - DOM node to remove the cursor style from.
+ */
+ removeCursors: function(element)
+ {
+ if (element.style != null)
+ {
+ element.style.cursor = '';
+ }
+
+ var children = element.childNodes;
+
+ if (children != null)
+ {
+ var childCount = children.length;
+
+ for (var i = 0; i < childCount; i += 1)
+ {
+ mxUtils.removeCursors(children[i]);
+ }
+ }
+ },
+
+ /**
+ * Function: repaintGraph
+ *
+ * Normally not required, this contains the code to workaround a repaint
+ * issue and force a repaint of the graph container in AppleWebKit.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to be repainted.
+ * pt - <mxPoint> where the dummy element should be placed.
+ */
+ repaintGraph: function(graph, pt)
+ {
+ if (mxClient.IS_GC || mxClient.IS_SF || mxClient.IS_OP)
+ {
+ var c = graph.container;
+
+ if (c != null && pt != null && (c.scrollLeft > 0 || c.scrollTop > 0))
+ {
+ var dummy = document.createElement('div');
+ dummy.style.position = 'absolute';
+ dummy.style.left = pt.x + 'px';
+ dummy.style.top = pt.y + 'px';
+ dummy.style.width = '1px';
+ dummy.style.height = '1px';
+
+ c.appendChild(dummy);
+ c.removeChild(dummy);
+ }
+ }
+ },
+
+ /**
+ * 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)
+ {
+ return function(element)
+ {
+ return (element != null) ? element.currentStyle : null;
+ };
+ }
+ else
+ {
+ return function(element)
+ {
+ return (element != null) ?
+ window.getComputedStyle(element, '') :
+ null;
+ };
+ }
+ }(),
+
+ /**
+ * 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)
+ {
+ 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: findNodeByAttribute
+ *
+ * Returns the first node where the given attribute matches the given value.
+ *
+ * Parameters:
+ *
+ * node - Root node where the search should start.
+ * attr - Name of the attribute to be checked.
+ * value - Value of the attribute to match.
+ */
+ findNodeByAttribute: function()
+ {
+ // Workaround for missing XPath support in IE9
+ if (document.documentMode >= 9)
+ {
+ return function(node, attr, value)
+ {
+ var result = null;
+
+ if (node != null)
+ {
+ if (node.nodeType == mxConstants.NODETYPE_ELEMENT && node.getAttribute(attr) == value)
+ {
+ result = node;
+ }
+ else
+ {
+ var child = node.firstChild;
+
+ while (child != null && result == null)
+ {
+ result = mxUtils.findNodeByAttribute(child, attr, value);
+ child = child.nextSibling;
+ }
+ }
+ }
+
+ return result;
+ };
+ }
+ else if (mxClient.IS_IE)
+ {
+ return function(node, attr, value)
+ {
+ if (node == null)
+ {
+ return null;
+ }
+ else
+ {
+ var expr = '//*[@' + attr + '=\'' + value + '\']';
+
+ return node.ownerDocument.selectSingleNode(expr);
+ }
+ };
+ }
+ else
+ {
+ return function(node, attr, value)
+ {
+ if (node == null)
+ {
+ return null;
+ }
+ else
+ {
+ var result = node.ownerDocument.evaluate(
+ '//*[@' + attr + '=\'' + value + '\']',
+ node.ownerDocument, null,
+ XPathResult.ANY_TYPE, null);
+
+ return result.iterateNext();
+ }
+ };
+ }
+ }(),
+
+ /**
+ * 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
+ {
+ var tmp = f.toString();
+ var idx1 = 9;
+
+ while (tmp.charAt(idx1) == ' ')
+ {
+ idx1++;
+ }
+
+ var idx2 = tmp.indexOf('(', idx1);
+ str = tmp.substring(idx1, idx2);
+ }
+ }
+
+ return str;
+ },
+
+ /**
+ * Function: indexOf
+ *
+ * Returns the index of obj in array or -1 if the array does not contains
+ * 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: 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: getChildNodes
+ *
+ * Returns an array of child nodes that are of the given node type.
+ *
+ * Parameters:
+ *
+ * node - Parent DOM node to return the children from.
+ * nodeType - Optional node type to return. Default is
+ * <mxConstants.NODETYPE_ELEMENT>.
+ */
+ getChildNodes: function(node, nodeType)
+ {
+ nodeType = nodeType || mxConstants.NODETYPE_ELEMENT;
+
+ var children = [];
+ var tmp = node.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == nodeType)
+ {
+ children.push(tmp);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+
+ return children;
+ },
+
+ /**
+ * Function: 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 (window.ActiveXObject)
+ {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ }
+
+ return doc;
+ },
+
+ /**
+ * Function: parseXml
+ *
+ * Parses the specified XML string into a new XML document and returns the
+ * new document.
+ *
+ * Example:
+ *
+ * (code)
+ * var doc = mxUtils.parseXml(
+ * '<mxGraphModel><root><MyDiagram id="0"><mxCell/></MyDiagram>'+
+ * '<MyLayer id="1"><mxCell parent="0" /></MyLayer><MyObject id="2">'+
+ * '<mxCell style="strokeColor=blue;fillColor=red" parent="1" vertex="1">'+
+ * '<mxGeometry x="10" y="10" width="80" height="30" as="geometry"/>'+
+ * '</mxCell></MyObject></root></mxGraphModel>');
+ * (end)
+ *
+ * Parameters:
+ *
+ * xml - String that contains the XML data.
+ */
+ parseXml: function()
+ {
+ if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+ {
+ return function(xml)
+ {
+ var result = mxUtils.createXmlDocument();
+
+ result.async = 'false';
+ result.loadXML(xml);
+
+ return result;
+ };
+ }
+ else
+ {
+ return function(xml)
+ {
+ var parser = new DOMParser();
+
+ return parser.parseFromString(xml, 'text/xml');
+ };
+ }
+ }(),
+
+ /**
+ * 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()
+ {
+ window.getSelection().removeAllRanges();
+ };
+ }
+ }(),
+
+ /**
+ * Function: getPrettyXML
+ *
+ * Returns a pretty printed string that represents the XML tree for the
+ * given node. This method should only be used to print XML for reading,
+ * use <getXml> instead to obtain a string for processing.
+ *
+ * Parameters:
+ *
+ * node - DOM node to return the XML for.
+ * tab - Optional string that specifies the indentation for one level.
+ * Default is two spaces.
+ * indent - Optional string that represents the current indentation.
+ * Default is an empty string.
+ */
+ getPrettyXml: function(node, tab, indent)
+ {
+ var result = [];
+
+ if (node != null)
+ {
+ tab = tab || ' ';
+ indent = indent || '';
+
+ if (node.nodeType == mxConstants.NODETYPE_TEXT)
+ {
+ result.push(node.nodeValue);
+ }
+ 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].nodeValue);
+ result.push(' ' + attrs[i].nodeName +
+ '="' + val + '"');
+ }
+ }
+
+ // Recursively creates the XML string for each
+ // child nodes and appends it here with an
+ // indentation
+ var tmp = node.firstChild;
+
+ if (tmp != null)
+ {
+ result.push('>\n');
+
+ while (tmp != null)
+ {
+ result.push(mxUtils.getPrettyXml(
+ tmp, tab, indent + tab));
+ tmp = tmp.nextSibling;
+ }
+
+ result.push(indent + '</'+node.nodeName+'>\n');
+ }
+ else
+ {
+ result.push('/>\n');
+ }
+ }
+ }
+
+ return result.join('');
+ },
+
+ /**
+ * 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 = s || '';
+
+ s = s.replace(/&/g,'&amp;'); // 38 26
+ s = s.replace(/"/g,'&quot;'); // 34 22
+ s = s.replace(/\'/g,'&#39;'); // 39 27
+ s = s.replace(/</g,'&lt;'); // 60 3C
+ s = s.replace(/>/g,'&gt;'); // 62 3E
+
+ if (newline == null || newline)
+ {
+ s = s.replace(/\n/g, '&#xa;');
+ }
+
+ return s;
+ },
+
+ /**
+ * Function: isVml
+ *
+ * Returns true if the given node is in the VML namespace.
+ *
+ * Parameters:
+ *
+ * node - DOM node whose tag urn should be checked.
+ */
+ isVml: function(node)
+ {
+ return node != null && node.tagUrn == 'urn:schemas-microsoft-com:vml';
+ },
+
+ /**
+ * Function: getXml
+ *
+ * Returns the XML content of the specified node. For Internet Explorer,
+ * all \r\n\t[\t]* are removed from the XML string and the remaining \r\n
+ * are replaced by \n. All \n are then replaced with linefeed, or &#xa; if
+ * no linefeed is defined.
+ *
+ * Parameters:
+ *
+ * node - DOM node to return the XML for.
+ * linefeed - Optional string that linefeeds are converted into. Default is
+ * &#xa;
+ */
+ getXml: function(node, linefeed)
+ {
+ var xml = '';
+
+ if (node != null)
+ {
+ xml = node.xml;
+
+ if (xml == null)
+ {
+ if (node.innerHTML)
+ {
+ xml = node.innerHTML;
+ }
+ else
+ {
+ var xmlSerializer = new XMLSerializer();
+ xml = xmlSerializer.serializeToString(node);
+ }
+ }
+ else
+ {
+ xml = xml.replace(/\r\n\t[\t]*/g, '').
+ replace(/>\r\n/g, '>').
+ replace(/\r\n/g, '\n');
+ }
+ }
+
+ // Replaces linefeeds with HTML Entities.
+ linefeed = linefeed || '&#xa;';
+ xml = xml.replace(/\n/g, linefeed);
+
+ return xml;
+ },
+
+ /**
+ * Function: getTextContent
+ *
+ * Returns the text content of the specified node.
+ *
+ * Parameters:
+ *
+ * node - DOM node to return the text content for.
+ */
+ getTextContent: function(node)
+ {
+ var result = '';
+
+ if (node != null)
+ {
+ if (node.firstChild != null)
+ {
+ node = node.firstChild;
+ }
+
+ result = node.nodeValue || '';
+ }
+
+ return result;
+ },
+
+ /**
+ * 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].nodeValue;
+
+ if (value != null && value.length > 0)
+ {
+ tmp.push(' ');
+ tmp.push(attrs[i].nodeName);
+ tmp.push('="');
+ tmp.push(value);
+ tmp.push('"');
+ }
+ }
+ }
+
+ if (node.innerHTML.length == 0)
+ {
+ tmp.push('/>');
+ }
+ else
+ {
+ tmp.push('>');
+ tmp.push(node.innerHTML);
+ tmp.push('</'+node.nodeName+'>');
+ }
+
+ return tmp.join('');
+ }
+ }
+
+ return '';
+ };
+ }
+ else
+ {
+ return function(node)
+ {
+ if (node != null)
+ {
+ var serializer = new XMLSerializer();
+ return serializer.serializeToString(node);
+ }
+
+ return '';
+ };
+ }
+ }(),
+
+ /**
+ * Function: write
+ *
+ * Creates a text node for the given string and appends it to the given
+ * parent. Returns the text node.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to append the text node to.
+ * text - String representing the text to be added.
+ */
+ write: function(parent, text)
+ {
+ var doc = parent.ownerDocument;
+ var node = doc.createTextNode(text);
+
+ if (parent != null)
+ {
+ parent.appendChild(node);
+ }
+
+ return node;
+ },
+
+ /**
+ * Function: writeln
+ *
+ * Creates a text node for the given string and appends it to the given
+ * parent with an additional linefeed. Returns the text node.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to append the text node to.
+ * text - String representing the text to be added.
+ */
+ writeln: function(parent, text)
+ {
+ var doc = parent.ownerDocument;
+ var node = doc.createTextNode(text);
+
+ if (parent != null)
+ {
+ parent.appendChild(node);
+ parent.appendChild(document.createElement('br'));
+ }
+
+ return node;
+ },
+
+ /**
+ * Function: br
+ *
+ * Appends a linebreak to the given parent and returns the linebreak.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to append the linebreak to.
+ */
+ br: function(parent, count)
+ {
+ count = count || 1;
+ var br = null;
+
+ for (var i = 0; i < count; i++)
+ {
+ if (parent != null)
+ {
+ br = parent.ownerDocument.createElement('br');
+ parent.appendChild(br);
+ }
+ }
+
+ return br;
+ },
+
+ /**
+ * Function: button
+ *
+ * Returns a new button with the given level and function as an onclick
+ * event handler.
+ *
+ * (code)
+ * document.body.appendChild(mxUtils.button('Test', function(evt)
+ * {
+ * alert('Hello, World!');
+ * }));
+ * (end)
+ *
+ * Parameters:
+ *
+ * label - String that represents the label of the button.
+ * funct - Function to be called if the button is pressed.
+ * doc - Optional document to be used for creating the button. Default is the
+ * current document.
+ */
+ button: function(label, funct, doc)
+ {
+ doc = (doc != null) ? doc : document;
+
+ var button = doc.createElement('button');
+ mxUtils.write(button, label);
+
+ mxEvent.addListener(button, 'click', function(evt)
+ {
+ funct(evt);
+ });
+
+ return button;
+ },
+
+ /**
+ * Function: para
+ *
+ * Appends a new paragraph with the given text to the specified parent and
+ * returns the paragraph.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to append the text node to.
+ * text - String representing the text for the new paragraph.
+ */
+ para: function(parent, text)
+ {
+ var p = document.createElement('p');
+ mxUtils.write(p, text);
+
+ if (parent != null)
+ {
+ parent.appendChild(p);
+ }
+
+ return p;
+ },
+
+ /**
+ * Function: linkAction
+ *
+ * Adds a hyperlink to the specified parent that invokes action on the
+ * specified editor.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to contain the new link.
+ * text - String that is used as the link label.
+ * editor - <mxEditor> that will execute the action.
+ * action - String that defines the name of the action to be executed.
+ * pad - Optional left-padding for the link. Default is 0.
+ */
+ linkAction: function(parent, text, editor, action, pad)
+ {
+ return mxUtils.link(parent, text, function()
+ {
+ editor.execute(action);
+ }, pad);
+ },
+
+ /**
+ * Function: linkInvoke
+ *
+ * Adds a hyperlink to the specified parent that invokes the specified
+ * function on the editor passing along the specified argument. The
+ * function name is the name of a function of the editor instance,
+ * not an action name.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to contain the new link.
+ * text - String that is used as the link label.
+ * editor - <mxEditor> instance to execute the function on.
+ * functName - String that represents the name of the function.
+ * arg - Object that represents the argument to the function.
+ * pad - Optional left-padding for the link. Default is 0.
+ */
+ linkInvoke: function(parent, text, editor, functName, arg, pad)
+ {
+ return mxUtils.link(parent, text, function()
+ {
+ editor[functName](arg);
+ }, pad);
+ },
+
+ /**
+ * Function: link
+ *
+ * Adds a hyperlink to the specified parent and invokes the given function
+ * when the link is clicked.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to contain the new link.
+ * text - String that is used as the link label.
+ * funct - Function to execute when the link is clicked.
+ * pad - Optional left-padding for the link. Default is 0.
+ */
+ link: function(parent, text, funct, pad)
+ {
+ var a = document.createElement('span');
+
+ a.style.color = 'blue';
+ a.style.textDecoration = 'underline';
+ a.style.cursor = 'pointer';
+
+ if (pad != null)
+ {
+ a.style.paddingLeft = pad+'px';
+ }
+
+ mxEvent.addListener(a, 'click', funct);
+ mxUtils.write(a, text);
+
+ if (parent != null)
+ {
+ parent.appendChild(a);
+ }
+
+ return a;
+ },
+
+ /**
+ * Function: 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 left = parseInt(node.offsetLeft);
+ var width = parseInt(node.offsetWidth);
+
+ var b = document.body;
+ var d = document.documentElement;
+
+ var right = (b.scrollLeft || d.scrollLeft) +
+ (b.clientWidth || d.clientWidth);
+
+ if (left + width > right)
+ {
+ node.style.left = Math.max((b.scrollLeft || d.scrollLeft),
+ right - width)+'px';
+ }
+
+ var top = parseInt(node.offsetTop);
+ var height = parseInt(node.offsetHeight);
+
+ var bottom = (b.scrollTop || d.scrollTop) +
+ Math.max(b.clientHeight || 0, d.clientHeight);
+
+ if (top + height > bottom)
+ {
+ node.style.top = Math.max((b.scrollTop || d.scrollTop),
+ bottom - height)+'px';
+ }
+ },
+
+ /**
+ * Function: open
+ *
+ * Opens the specified file from the local filesystem and returns the
+ * contents of the file as a string. This implementation requires an
+ * ActiveX object in IE and special privileges in Firefox. Relative
+ * filenames are only supported in IE and will go onto the users'
+ * Desktop. You may have to load
+ * chrome://global/content/contentAreaUtils.js to disable certain
+ * security restrictions in Mozilla for this to work.
+ *
+ * See known-issues before using this function.
+ *
+ * Example:
+ * (code)
+ * var data = mxUtils.open('C:\\temp\\test.txt');
+ * mxUtils.alert('Data: '+data);
+ * (end)
+ *
+ * Parameters:
+ *
+ * filename - String representing the local file name.
+ */
+ open: function(filename)
+ {
+ // Requests required privileges in Firefox
+ if (mxClient.IS_NS)
+ {
+ try
+ {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ }
+ catch (e)
+ {
+ mxUtils.alert('Permission to read file denied.');
+
+ return '';
+ }
+
+ var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
+ file.initWithPath(filename);
+
+ if (!file.exists())
+ {
+ mxUtils.alert('File not found.');
+ return '';
+ }
+
+ var is = Components.classes['@mozilla.org/network/file-input-stream;1'].createInstance(Components.interfaces.nsIFileInputStream);
+ is.init(file,0x01, 00004, null);
+
+ var sis = Components.classes['@mozilla.org/scriptableinputstream;1'].createInstance(Components.interfaces.nsIScriptableInputStream);
+ sis.init(is);
+
+ var output = sis.read(sis.available());
+
+ return output;
+ }
+ else
+ {
+ var activeXObject = new ActiveXObject('Scripting.FileSystemObject');
+
+ var newStream = activeXObject.OpenTextFile(filename, 1);
+ var text = newStream.readAll();
+ newStream.close();
+
+ return text;
+ }
+ },
+
+ /**
+ * Function: save
+ *
+ * Saves the specified content in the given file on the local file system.
+ * This implementation requires an ActiveX object in IE and special
+ * privileges in Firefox. Relative filenames are only supported in IE and
+ * will be loaded from the users' Desktop. You may have to load
+ * chrome://global/content/contentAreaUtils.js to disable certain
+ * security restrictions in Mozilla for this to work.
+ *
+ * See known-issues before using this function.
+ *
+ * Example:
+ *
+ * (code)
+ * var data = 'Hello, World!';
+ * mxUtils.save('C:\\test.txt', data);
+ * (end)
+ *
+ * Parameters:
+ *
+ * filename - String representing the local file name.
+ */
+ save: function(filename, content)
+ {
+ if (mxClient.IS_NS)
+ {
+ try
+ {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ }
+ catch (e)
+ {
+ mxUtils.alert('Permission to write file denied.');
+ return;
+ }
+
+ var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
+ file.initWithPath(filename);
+
+ if (!file.exists())
+ {
+ file.create(0x00, 0644);
+ }
+
+ var outputStream = Components.classes['@mozilla.org/network/file-output-stream;1'].createInstance(Components.interfaces.nsIFileOutputStream);
+
+ outputStream.init(file, 0x20 | 0x02,00004, null);
+ outputStream.write(content, content.length);
+ outputStream.flush();
+ outputStream.close();
+ }
+ else
+ {
+ var fso = new ActiveXObject('Scripting.FileSystemObject');
+
+ var file = fso.CreateTextFile(filename, true);
+ file.Write(content);
+ file.Close();
+ }
+ },
+
+ /**
+ * Function: saveAs
+ *
+ * Saves the specified content by displaying a dialog to save the content
+ * as a file on the local filesystem. This implementation does not use an
+ * ActiveX object in IE, however, it does require special privileges in
+ * Firefox. You may have to load
+ * chrome://global/content/contentAreaUtils.js to disable certain
+ * security restrictions in Mozilla for this to work.
+ *
+ * See known-issues before using this function. It is not recommended using
+ * this function in production environment as access to the filesystem
+ * cannot be guaranteed in Firefox. The following code is used in
+ * Firefox to try and enable saving to the filesystem.
+ *
+ * (code)
+ * netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ * (end)
+ *
+ * Example:
+ *
+ * (code)
+ * mxUtils.saveAs('Hello, World!');
+ * (end)
+ *
+ * Parameters:
+ *
+ * content - String representing the file's content.
+ */
+ saveAs: function(content)
+ {
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src', '');
+ iframe.style.visibility = 'hidden';
+ document.body.appendChild(iframe);
+
+ try
+ {
+ if (mxClient.IS_NS)
+ {
+ var doc = iframe.contentDocument;
+
+ doc.open();
+ doc.write(content);
+ doc.close();
+
+ try
+ {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ // LATER: Remove existing HTML markup in file
+ iframe.focus();
+ saveDocument(doc);
+ }
+ catch (e)
+ {
+ mxUtils.alert('Permission to save document denied.');
+ }
+ }
+ else
+ {
+ var doc = iframe.contentWindow.document;
+ doc.write(content);
+ doc.execCommand('SaveAs', false, document.location);
+ }
+ }
+ finally
+ {
+ document.body.removeChild(iframe);
+ }
+ },
+
+ /**
+ * Function: copy
+ *
+ * Copies the specified content to the local clipboard. This implementation
+ * requires special privileges in Firefox. You may have to load
+ * chrome://global/content/contentAreaUtils.js to disable certain
+ * security restrictions in Mozilla for this to work.
+ *
+ * Parameters:
+ *
+ * content - String to be copied to the clipboard.
+ */
+ copy: function(content)
+ {
+ if (window.clipboardData)
+ {
+ window.clipboardData.setData('Text', content);
+ }
+ else
+ {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+ var clip = Components.classes['@mozilla.org/widget/clipboard;1']
+ .createInstance(Components.interfaces.nsIClipboard);
+
+ if (!clip)
+ {
+ return;
+ }
+
+ var trans = Components.classes['@mozilla.org/widget/transferable;1']
+ .createInstance(Components.interfaces.nsITransferable);
+
+ if (!trans)
+ {
+ return;
+ }
+
+ trans.addDataFlavor('text/unicode');
+ var str = Components.classes['@mozilla.org/supports-string;1']
+ .createInstance(Components.interfaces.nsISupportsString);
+
+ var copytext=content;
+ str.data=copytext;
+ trans.setTransferData('text/unicode', str, copytext.length*2);
+ var clipid=Components.interfaces.nsIClipboard;
+
+ clip.setData(trans,null,clipid.kGlobalClipboard);
+ }
+ },
+
+ /**
+ * Function: load
+ *
+ * Loads the specified URL *synchronously* and returns the <mxXmlRequest>.
+ * Throws an exception if the file cannot be loaded. See <mxUtils.get> for
+ * an asynchronous implementation.
+ *
+ * Example:
+ *
+ * (code)
+ * try
+ * {
+ * var req = mxUtils.load(filename);
+ * var root = req.getDocumentElement();
+ * // Process XML DOM...
+ * }
+ * catch (ex)
+ * {
+ * mxUtils.alert('Cannot load '+filename+': '+ex);
+ * }
+ * (end)
+ *
+ * Parameters:
+ *
+ * url - URL to get the data from.
+ */
+ load: function(url)
+ {
+ var req = new mxXmlRequest(url, null, 'GET', false);
+ req.send();
+
+ return req;
+ },
+
+ /**
+ * Function: get
+ *
+ * Loads the specified URL *asynchronously* and invokes the given functions
+ * depending on the request status. Returns the <mxXmlRequest> in use. Both
+ * functions take the <mxXmlRequest> as the only parameter. See
+ * <mxUtils.load> for a synchronous implementation.
+ *
+ * Example:
+ *
+ * (code)
+ * mxUtils.get(url, function(req)
+ * {
+ * var node = req.getDocumentElement();
+ * // Process XML DOM...
+ * });
+ * (end)
+ *
+ * So for example, to load a diagram into an existing graph model, the
+ * following code is used.
+ *
+ * (code)
+ * mxUtils.get(url, function(req)
+ * {
+ * var node = req.getDocumentElement();
+ * var dec = new mxCodec(node.ownerDocument);
+ * dec.decode(node, graph.getModel());
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * url - URL to get the data from.
+ * onload - Optional function to execute for a successful response.
+ * onerror - Optional function to execute on error.
+ */
+ get: function(url, onload, onerror)
+ {
+ return new mxXmlRequest(url, null, 'GET').send(onload, onerror);
+ },
+
+ /**
+ * Function: post
+ *
+ * Posts the specified params to the given URL *asynchronously* and invokes
+ * the given functions depending on the request status. Returns the
+ * <mxXmlRequest> in use. Both functions take the <mxXmlRequest> as the
+ * only parameter. Make sure to use encodeURIComponent for the parameter
+ * values.
+ *
+ * Example:
+ *
+ * (code)
+ * mxUtils.post(url, 'key=value', function(req)
+ * {
+ * mxUtils.alert('Ready: '+req.isReady()+' Status: '+req.getStatus());
+ * // Process req.getDocumentElement() using DOM API if OK...
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * url - URL to get the data from.
+ * params - Parameters for the post request.
+ * onload - Optional function to execute for a successful response.
+ * onerror - Optional function to execute on error.
+ */
+ post: function(url, params, onload, onerror)
+ {
+ return new mxXmlRequest(url, params).send(onload, onerror);
+ },
+
+ /**
+ * Function: submit
+ *
+ * Submits the given parameters to the specified URL using
+ * <mxXmlRequest.simulate> and returns the <mxXmlRequest>.
+ * Make sure to use encodeURIComponent for the parameter
+ * values.
+ *
+ * Parameters:
+ *
+ * url - URL to get the data from.
+ * params - Parameters for the form.
+ * doc - Document to create the form in.
+ * target - Target to send the form result to.
+ */
+ submit: function(url, params, doc, target)
+ {
+ return new mxXmlRequest(url, params).simulate(doc, target);
+ },
+
+ /**
+ * Function: loadInto
+ *
+ * Loads the specified URL *asynchronously* into the specified document,
+ * invoking onload after the document has been loaded. This implementation
+ * does not use <mxXmlRequest>, but the document.load method.
+ *
+ * Parameters:
+ *
+ * url - URL to get the data from.
+ * doc - The document to load the URL into.
+ * onload - Function to execute when the URL has been loaded.
+ */
+ loadInto: function(url, doc, onload)
+ {
+ if (mxClient.IS_IE)
+ {
+ doc.onreadystatechange = function ()
+ {
+ if (doc.readyState == 4)
+ {
+ onload();
+ }
+ };
+ }
+ else
+ {
+ doc.addEventListener('load', onload, false);
+ }
+
+ doc.load(url);
+ },
+
+ /**
+ * Function: getValue
+ *
+ * Returns the value for the given key in the given associative array or
+ * the given default value if the value is null.
+ *
+ * Parameters:
+ *
+ * array - Associative array that contains the value for the key.
+ * key - Key whose value should be returned.
+ * defaultValue - Value to be returned if the value for the given
+ * key is null.
+ */
+ getValue: function(array, key, defaultValue)
+ {
+ var value = (array != null) ? array[key] : null;
+
+ if (value == null)
+ {
+ value = defaultValue;
+ }
+
+ return value;
+ },
+
+ /**
+ * Function: getNumber
+ *
+ * Returns the numeric value for the given key in the given associative
+ * array or the given default value (or 0) if the value is null. The value
+ * is converted to a numeric value using the Number function.
+ *
+ * Parameters:
+ *
+ * array - Associative array that contains the value for the key.
+ * key - Key whose value should be returned.
+ * defaultValue - Value to be returned if the value for the given
+ * key is null. Default is 0.
+ */
+ getNumber: function(array, key, defaultValue)
+ {
+ var value = (array != null) ? array[key] : null;
+
+ if (value == null)
+ {
+ value = defaultValue || 0;
+ }
+
+ return Number(value);
+ },
+
+ /**
+ * Function: getColor
+ *
+ * Returns the color value for the given key in the given associative
+ * array or the given default value if the value is null. If the value
+ * is <mxConstants.NONE> then null is returned.
+ *
+ * Parameters:
+ *
+ * array - Associative array that contains the value for the key.
+ * key - Key whose value should be returned.
+ * defaultValue - Value to be returned if the value for the given
+ * key is null. Default is null.
+ */
+ getColor: function(array, key, defaultValue)
+ {
+ var value = (array != null) ? array[key] : null;
+
+ if (value == null)
+ {
+ value = defaultValue;
+ }
+ else if (value == mxConstants.NONE)
+ {
+ value = null;
+ }
+
+ return value;
+ },
+
+ /**
+ * Function: clone
+ *
+ * Recursively clones the specified object ignoring all fieldnames in the
+ * given array of transient fields. <mxObjectIdentity.FIELD_NAME> is always
+ * ignored by this function.
+ *
+ * Parameters:
+ *
+ * obj - Object to be cloned.
+ * transients - Optional array of strings representing the fieldname to be
+ * ignored.
+ * shallow - Optional boolean argument to specify if a shallow clone should
+ * be created, that is, one where all object references are not cloned or,
+ * in other words, one where only atomic (strings, numbers) values are
+ * cloned. Default is false.
+ */
+ clone: function(obj, transients, shallow)
+ {
+ shallow = (shallow != null) ? shallow : false;
+ var clone = null;
+
+ if (obj != null && typeof(obj.constructor) == 'function')
+ {
+ clone = new obj.constructor();
+
+ for (var i in obj)
+ {
+ if (i != mxObjectIdentity.FIELD_NAME && (transients == null ||
+ mxUtils.indexOf(transients, i) < 0))
+ {
+ if (!shallow && typeof(obj[i]) == 'object')
+ {
+ clone[i] = mxUtils.clone(obj[i]);
+ }
+ else
+ {
+ clone[i] = obj[i];
+ }
+ }
+ }
+ }
+
+ return clone;
+ },
+
+ /**
+ * Function: equalPoints
+ *
+ * Compares all mxPoints in the given lists.
+ *
+ * Parameters:
+ *
+ * a - Array of <mxPoints> to be compared.
+ * b - Array of <mxPoints> to be compared.
+ */
+ equalPoints: function(a, b)
+ {
+ if ((a == null && b != null) || (a != null && b == null) ||
+ (a != null && b != null && a.length != b.length))
+ {
+ return false;
+ }
+ else if (a != null && b != null)
+ {
+ for (var i = 0; i < a.length; i++)
+ {
+ if (a[i] == b[i] || (a[i] != null && !a[i].equals(b[i])))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Function: equalEntries
+ *
+ * Compares all entries in the given dictionaries.
+ *
+ * Parameters:
+ *
+ * a - <mxRectangle> to be compared.
+ * b - <mxRectangle> to be compared.
+ */
+ equalEntries: 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 key in a)
+ {
+ if (a[key] != b[key])
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * 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: 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.
+ */
+ getBoundingBox: function(rect, rotation)
+ {
+ 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);
+
+ var cx = new mxPoint(
+ rect.x + rect.width / 2,
+ rect.y + rect.height / 2);
+
+ var p1 = new mxPoint(rect.x, rect.y);
+ var p2 = new mxPoint(rect.x + rect.width, rect.y);
+ var p3 = new mxPoint(p2.x, rect.y + rect.height);
+ var p4 = new mxPoint(rect.x, p3.y);
+
+ p1 = mxUtils.getRotatedPoint(p1, cos, sin, cx);
+ p2 = mxUtils.getRotatedPoint(p2, cos, sin, cx);
+ p3 = mxUtils.getRotatedPoint(p3, cos, sin, cx);
+ p4 = mxUtils.getRotatedPoint(p4, cos, sin, cx);
+
+ result = new mxRectangle(p1.x, p1.y, 0, 0);
+ result.add(new mxRectangle(p2.x, p2.y, 0, 0));
+ result.add(new mxRectangle(p3.x, p3.y, 0, 0));
+ result.add(new mxRectangle(p4.x, p4.y, 0, 0));
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: getRotatedPoint
+ *
+ * Rotates the given point by the given cos and sin.
+ */
+ getRotatedPoint: function(pt, cos, sin, c)
+ {
+ c = (c != null) ? c : new mxPoint();
+ var x = pt.x - c.x;
+ var y = pt.y - c.y;
+
+ var x1 = x * cos - y * sin;
+ var y1 = y * cos + x * sin;
+
+ return new mxPoint(x1 + c.x, y1 + c.y);
+ },
+
+ /**
+ * Returns an integer mask of the port constraints of the given map
+ * @param dict the style map to determine the port constraints for
+ * @param defaultValue Default value to return if the key is undefined.
+ * @return the mask of port constraint directions
+ *
+ * Parameters:
+ *
+ * terminal - <mxCelState> that represents the terminal.
+ * edge - <mxCellState> that represents the edge.
+ * source - Boolean that specifies if the terminal is the source terminal.
+ * defaultValue - Default value to be returned.
+ */
+ getPortConstraints: function(terminal, edge, source, defaultValue)
+ {
+ var value = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT, null);
+
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ else
+ {
+ var directions = value.toString();
+ var returnValue = mxConstants.DIRECTION_MASK_NONE;
+
+ if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0)
+ {
+ returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+ }
+ if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0)
+ {
+ returnValue |= mxConstants.DIRECTION_MASK_WEST;
+ }
+ if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0)
+ {
+ returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+ }
+ if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0)
+ {
+ returnValue |= mxConstants.DIRECTION_MASK_EAST;
+ }
+
+ 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: rectangleIntersectsSegment
+ *
+ * Returns true if the given rectangle intersects the given segment.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that represents the rectangle.
+ * p1 - <mxPoint> that represents the first point of the segment.
+ * p2 - <mxPoint> that represents the second point of the segment.
+ */
+ rectangleIntersectsSegment: function(bounds, p1, p2)
+ {
+ var top = bounds.y;
+ var left = bounds.x;
+ var bottom = top + bounds.height;
+ var right = left + bounds.width;
+
+ // Find min and max X for the segment
+ var minX = p1.x;
+ var maxX = p2.x;
+
+ if (p1.x > p2.x)
+ {
+ minX = p2.x;
+ maxX = p1.x;
+ }
+
+ // Find the intersection of the segment's and rectangle's x-projections
+ if (maxX > right)
+ {
+ maxX = right;
+ }
+
+ if (minX < left)
+ {
+ minX = left;
+ }
+
+ if (minX > maxX) // If their projections do not intersect return false
+ {
+ return false;
+ }
+
+ // Find corresponding min and max Y for min and max X we found before
+ var minY = p1.y;
+ var maxY = p2.y;
+ var dx = p2.x - p1.x;
+
+ if (Math.abs(dx) > 0.0000001)
+ {
+ var a = (p2.y - p1.y) / dx;
+ var b = p1.y - a * p1.x;
+ minY = a * minX + b;
+ maxY = a * maxX + b;
+ }
+
+ if (minY > maxY)
+ {
+ var tmp = maxY;
+ maxY = minY;
+ minY = tmp;
+ }
+
+ // Find the intersection of the segment's and rectangle's y-projections
+ if (maxY > bottom)
+ {
+ maxY = bottom;
+ }
+
+ if (minY < top)
+ {
+ minY = top;
+ }
+
+ if (minY > maxY) // If Y-projections do not intersect return false
+ {
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Function: contains
+ *
+ * Returns true if the specified point (x, y) is contained in the given rectangle.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that represents the area.
+ * x - X-coordinate of the point.
+ * y - Y-coordinate of the point.
+ */
+ contains: function(bounds, x, y)
+ {
+ return (bounds.x <= x && bounds.x + bounds.width >= x &&
+ bounds.y <= y && bounds.y + bounds.height >= y);
+ },
+
+ /**
+ * Function: intersects
+ *
+ * Returns true if the two rectangles intersect.
+ *
+ * Parameters:
+ *
+ * a - <mxRectangle> to be checked for intersection.
+ * b - <mxRectangle> to be checked for intersection.
+ */
+ intersects: function(a, b)
+ {
+ var tw = a.width;
+ var th = a.height;
+ var rw = b.width;
+ var rh = b.height;
+
+ if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0)
+ {
+ return false;
+ }
+
+ var tx = a.x;
+ var ty = a.y;
+ var rx = b.x;
+ var ry = b.y;
+
+ rw += rx;
+ rh += ry;
+ tw += tx;
+ th += ty;
+
+ return ((rw < rx || rw > tx) &&
+ (rh < ry || rh > ty) &&
+ (tw < tx || tw > rx) &&
+ (th < ty || th > ry));
+ },
+
+ /**
+ * Function: intersects
+ *
+ * Returns true if the two rectangles intersect.
+ *
+ * Parameters:
+ *
+ * a - <mxRectangle> to be checked for intersection.
+ * b - <mxRectangle> 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);
+
+ return mxUtils.contains(rect, x, y);
+ }
+
+ return true;
+ },
+
+ /**
+ * Function: getOffset
+ *
+ * Returns the offset for the specified container as an <mxPoint>. The
+ * offset is the distance from the top left corner of the container to the
+ * top left corner of the document.
+ *
+ * Parameters:
+ *
+ * container - DOM node to return the offset for.
+ * scollOffset - Optional boolean to add the scroll offset of the document.
+ * Default is false.
+ */
+ getOffset: function(container, scrollOffset)
+ {
+ var offsetLeft = 0;
+ var offsetTop = 0;
+
+ if (scrollOffset != null && scrollOffset)
+ {
+ var b = document.body;
+ var d = document.documentElement;
+ offsetLeft += (b.scrollLeft || d.scrollLeft);
+ offsetTop += (b.scrollTop || d.scrollTop);
+ }
+
+ while (container.offsetParent)
+ {
+ offsetLeft += container.offsetLeft;
+ offsetTop += container.offsetTop;
+
+ container = container.offsetParent;
+ }
+
+ return new mxPoint(offsetLeft, offsetTop);
+ },
+
+ /**
+ * Function: getScrollOrigin
+ *
+ * Returns the top, left corner of the viewrect as an <mxPoint>.
+ */
+ getScrollOrigin: function(node)
+ {
+ var b = document.body;
+ var d = document.documentElement;
+ var sl = (b.scrollLeft || d.scrollLeft);
+ var st = (b.scrollTop || d.scrollTop);
+
+ var result = new mxPoint(sl, st);
+
+ while (node != null && node != b && node != d)
+ {
+ if (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop))
+ {
+ result.x += node.scrollLeft;
+ result.y += node.scrollTop;
+ }
+
+ node = node.parentNode;
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: convertPoint
+ *
+ * Converts the specified point (x, y) using the offset of the specified
+ * container and returns a new <mxPoint> with the result.
+ *
+ * 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);
+ 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, 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
+ */
+ ltrim: function(str, chars)
+ {
+ chars = chars || "\\s";
+
+ return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
+ },
+
+ /**
+ * Function: rtrim
+ *
+ * Strips all whitespaces from the 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
+ */
+ rtrim: function(str, chars)
+ {
+ chars = chars || "\\s";
+
+ return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
+ },
+
+ /**
+ * 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:
+ *
+ * str - String representing the possibly numeric value.
+ */
+ isNumeric: function(str)
+ {
+ return str != null && (str.length == null || (str.length > 0 &&
+ str.indexOf('0x') < 0) && str.indexOf('0X') < 0) && !isNaN(str);
+ },
+
+ /**
+ * Function: mod
+ *
+ * Returns the remainder of division of n by m. You should use this instead
+ * of the built-in operation as the built-in operation does not properly
+ * handle negative numbers.
+ */
+ mod: function(n, m)
+ {
+ return ((n % m) + m) % m;
+ },
+
+ /**
+ * Function: intersection
+ *
+ * Returns the intersection of two lines as an <mxPoint>.
+ *
+ * Parameters:
+ *
+ * x0 - X-coordinate of the first line's startpoint.
+ * y0 - X-coordinate of the first line's startpoint.
+ * x1 - X-coordinate of the first line's endpoint.
+ * y1 - Y-coordinate of the first line's endpoint.
+ * x2 - X-coordinate of the second line's startpoint.
+ * y2 - Y-coordinate of the second line's startpoint.
+ * x3 - X-coordinate of the second line's endpoint.
+ * y3 - Y-coordinate of the second line's endpoint.
+ */
+ intersection: function (x0, y0, x1, y1, x2, y2, x3, y3)
+ {
+ var denom = ((y3 - y2)*(x1 - x0)) - ((x3 - x2)*(y1 - y0));
+ var nume_a = ((x3 - x2)*(y0 - y2)) - ((y3 - y2)*(x0 - x2));
+ var nume_b = ((x1 - x0)*(y0 - y2)) - ((y1 - y0)*(x0 - x2));
+
+ var ua = nume_a / denom;
+ var ub = nume_b / denom;
+
+ if(ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0)
+ {
+ // Get the intersection point
+ var intersectionX = x0 + ua*(x1 - x0);
+ var intersectionY = y0 + ua*(y1 - y0);
+
+ return new mxPoint(intersectionX, intersectionY);
+ }
+
+ // No intersection
+ return null;
+ },
+
+ /**
+ * Function: ptSeqDistSq
+ *
+ * Returns the square distance between a segment and a point.
+ *
+ * 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: relativeCcw
+ *
+ * Returns 1 if the given point on the right side of the segment, 0 if its
+ * on the segment, and -1 if the point is on the left side of the segment.
+ *
+ * Parameters:
+ *
+ * x1 - X-coordinate of the startpoint of the segment.
+ * y1 - Y-coordinate of the startpoint of the segment.
+ * x2 - X-coordinate of the endpoint of the segment.
+ * y2 - Y-coordinate of the endpoint of the segment.
+ * px - X-coordinate of the point.
+ * py - Y-coordinate of the point.
+ */
+ relativeCcw: function(x1, y1, x2, y2, px, py)
+ {
+ x2 -= x1;
+ y2 -= y1;
+ px -= x1;
+ py -= y1;
+ var ccw = px * y2 - py * x2;
+
+ if (ccw == 0.0)
+ {
+ ccw = px * x2 + py * y2;
+
+ if (ccw > 0.0)
+ {
+ px -= x2;
+ py -= y2;
+ ccw = px * x2 + py * y2;
+
+ if (ccw < 0.0)
+ {
+ ccw = 0.0;
+ }
+ }
+ }
+
+ return (ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0);
+ },
+
+ /**
+ * Function: animateChanges
+ *
+ * See <mxEffects.animateChanges>. This is for backwards compatibility and
+ * will be removed later.
+ */
+ animateChanges: function(graph, changes)
+ {
+ // LATER: Deprecated, remove this function
+ mxEffects.animateChanges.apply(this, arguments);
+ },
+
+ /**
+ * Function: cascadeOpacity
+ *
+ * See <mxEffects.cascadeOpacity>. This is for backwards compatibility and
+ * will be removed later.
+ */
+ cascadeOpacity: function(graph, cell, opacity)
+ {
+ mxEffects.cascadeOpacity.apply(this, arguments);
+ },
+
+ /**
+ * Function: fadeOut
+ *
+ * See <mxEffects.fadeOut>. This is for backwards compatibility and
+ * will be removed later.
+ */
+ fadeOut: function(node, from, remove, step, delay, isEnabled)
+ {
+ mxEffects.fadeOut.apply(this, arguments);
+ },
+
+ /**
+ * Function: setOpacity
+ *
+ * Sets the opacity of the specified DOM node to the given value in %.
+ *
+ * Parameters:
+ *
+ * node - DOM node to set the opacity for.
+ * value - Opacity in %. Possible values are between 0 and 100.
+ */
+ setOpacity: function(node, value)
+ {
+ if (mxUtils.isVml(node))
+ {
+ if (value >= 100)
+ {
+ node.style.filter = null;
+ }
+ 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 = null;
+ }
+ 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
+ * quirs 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('v:image');
+ imageNode.setAttribute('src', src);
+ imageNode.style.borderStyle = 'none';
+ }
+ else
+ {
+ imageNode = document.createElement('img');
+ imageNode.setAttribute('src', src);
+ imageNode.setAttribute('border', '0');
+ }
+
+ return imageNode;
+ },
+
+ /**
+ * Function: sortCells
+ *
+ * Sorts the given cells according to the order in the cell hierarchy.
+ * Ascending is optional and defaults to true.
+ */
+ sortCells: function(cells, ascending)
+ {
+ ascending = (ascending != null) ? ascending : true;
+ var lookup = new mxDictionary();
+ cells.sort(function(o1, o2)
+ {
+ var p1 = lookup.get(o1);
+
+ if (p1 == null)
+ {
+ p1 = mxCellPath.create(o1).split(mxCellPath.PATH_SEPARATOR);
+ lookup.put(o1, p1);
+ }
+
+ var p2 = lookup.get(o2);
+
+ if (p2 == null)
+ {
+ p2 = mxCellPath.create(o2).split(mxCellPath.PATH_SEPARATOR);
+ lookup.put(o2, p2);
+ }
+
+ var comp = mxCellPath.compare(p1, p2);
+
+ return (comp == 0) ? 0 : (((comp > 0) == ascending) ? 1 : -1);
+ });
+
+ return cells;
+ },
+
+ /**
+ * Function: getStylename
+ *
+ * Returns the stylename in a style of the form [(stylename|key=value);] or
+ * an empty string if the given style does not contain a stylename.
+ *
+ * Parameters:
+ *
+ * style - String of the form [(stylename|key=value);].
+ */
+ getStylename: function(style)
+ {
+ if (style != null)
+ {
+ var pairs = style.split(';');
+ var stylename = pairs[0];
+
+ if (stylename.indexOf('=') < 0)
+ {
+ return stylename;
+ }
+ }
+
+ return '';
+ },
+
+ /**
+ * Function: getStylenames
+ *
+ * Returns the stylenames in a style of the form [(stylename|key=value);]
+ * or an empty array if the given style does not contain any stylenames.
+ *
+ * Parameters:
+ *
+ * style - String of the form [(stylename|key=value);].
+ */
+ getStylenames: function(style)
+ {
+ var result = [];
+
+ if (style != null)
+ {
+ var pairs = style.split(';');
+
+ for (var i = 0; i < pairs.length; i++)
+ {
+ if (pairs[i].indexOf('=') < 0)
+ {
+ result.push(pairs[i]);
+ }
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: indexOfStylename
+ *
+ * Returns the index of the given stylename in the given style. This
+ * returns -1 if the given stylename does not occur (as a stylename) in the
+ * given style, otherwise it returns the index of the first character.
+ */
+ indexOfStylename: function(style, stylename)
+ {
+ if (style != null && stylename != null)
+ {
+ var tokens = style.split(';');
+ var pos = 0;
+
+ for (var i = 0; i < tokens.length; i++)
+ {
+ if (tokens[i] == stylename)
+ {
+ return pos;
+ }
+
+ pos += tokens[i].length + 1;
+ }
+ }
+
+ return -1;
+ },
+
+ /**
+ * Function: addStylename
+ *
+ * Adds the specified stylename to the given style if it does not already
+ * contain the stylename.
+ */
+ addStylename: function(style, stylename)
+ {
+ if (mxUtils.indexOfStylename(style, stylename) < 0)
+ {
+ if (style == null)
+ {
+ style = '';
+ }
+ else if (style.length > 0 && style.charAt(style.length - 1) != ';')
+ {
+ style += ';';
+ }
+
+ style += stylename;
+ }
+
+ return style;
+ },
+
+ /**
+ * Function: removeStylename
+ *
+ * Removes all occurrences of the specified stylename in the given style
+ * and returns the updated style. Trailing semicolons are not preserved.
+ */
+ removeStylename: function(style, stylename)
+ {
+ var result = [];
+
+ if (style != null)
+ {
+ var tokens = style.split(';');
+
+ for (var i = 0; i < tokens.length; i++)
+ {
+ if (tokens[i] != stylename)
+ {
+ result.push(tokens[i]);
+ }
+ }
+ }
+
+ return result.join(';');
+ },
+
+ /**
+ * Function: removeAllStylenames
+ *
+ * Removes all stylenames from the given style and returns the updated
+ * style.
+ */
+ removeAllStylenames: function(style)
+ {
+ var result = [];
+
+ if (style != null)
+ {
+ var tokens = style.split(';');
+
+ for (var i = 0; i < tokens.length; i++)
+ {
+ // Keeps the key, value assignments
+ if (tokens[i].indexOf('=') >= 0)
+ {
+ result.push(tokens[i]);
+ }
+ }
+ }
+
+ return result.join(';');
+ },
+
+ /**
+ * Function: setCellStyles
+ *
+ * Assigns the value for the given key in the styles of the given cells, or
+ * removes the key from the styles if the value is null.
+ *
+ * Parameters:
+ *
+ * model - <mxGraphModel> to execute the transaction in.
+ * cells - Array of <mxCells> to be updated.
+ * key - Key of the style to be changed.
+ * value - New value for the given key.
+ */
+ setCellStyles: function(model, cells, key, value)
+ {
+ if (cells != null && cells.length > 0)
+ {
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != null)
+ {
+ var style = mxUtils.setStyle(
+ model.getStyle(cells[i]),
+ key, value);
+ model.setStyle(cells[i], style);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ },
+
+ /**
+ * Function: setStyle
+ *
+ * Adds or removes the given key, value pair to the style and returns the
+ * new style. If value is null or zero length then the key is removed from
+ * the style. This is for cell styles, not for CSS styles.
+ *
+ * Parameters:
+ *
+ * style - String of the form [(stylename|key=value);].
+ * key - Key of the style to be changed.
+ * value - New value for the given key.
+ */
+ setStyle: function(style, key, value)
+ {
+ var isValue = value != null && (typeof(value.length) == 'undefined' || value.length > 0);
+
+ if (style == null || style.length == 0)
+ {
+ if (isValue)
+ {
+ style = key+'='+value;
+ }
+ }
+ else
+ {
+ var index = style.indexOf(key+'=');
+
+ if (index < 0)
+ {
+ if (isValue)
+ {
+ var sep = (style.charAt(style.length-1) == ';') ? '' : ';';
+ style = style + sep + key+'='+value;
+ }
+ }
+ else
+ {
+ var tmp = (isValue) ? (key + '=' + value) : '';
+ var cont = style.indexOf(';', index);
+
+ if (!isValue)
+ {
+ cont++;
+ }
+
+ style = style.substring(0, index) + tmp +
+ ((cont > index) ? style.substring(cont) : '');
+ }
+ }
+
+ return style;
+ },
+
+ /**
+ * Function: setCellStyleFlags
+ *
+ * Sets or toggles the flag bit for the given key in the cell's styles.
+ * If value is null then the flag is toggled.
+ *
+ * Example:
+ *
+ * (code)
+ * var cells = graph.getSelectionCells();
+ * mxUtils.setCellStyleFlags(graph.model,
+ * cells,
+ * mxConstants.STYLE_FONTSTYLE,
+ * mxConstants.FONT_BOLD);
+ * (end)
+ *
+ * Toggles the bold font style.
+ *
+ * Parameters:
+ *
+ * model - <mxGraphModel> that contains the cells.
+ * cells - Array of <mxCells> to change the style for.
+ * key - Key of the style to be changed.
+ * flag - Integer for the bit to be changed.
+ * value - Optional boolean value for the flag.
+ */
+ setCellStyleFlags: function(model, cells, key, flag, value)
+ {
+ if (cells != null && cells.length > 0)
+ {
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != null)
+ {
+ var style = mxUtils.setStyleFlag(
+ model.getStyle(cells[i]),
+ key, flag, value);
+ model.setStyle(cells[i], style);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ },
+
+ /**
+ * Function: setStyleFlag
+ *
+ * Sets or removes the given key from the specified style and returns the
+ * new style. If value is null then the flag is toggled.
+ *
+ * Parameters:
+ *
+ * style - String of the form [(stylename|key=value);].
+ * key - Key of the style to be changed.
+ * flag - Integer for the bit to be changed.
+ * value - Optional boolean value for the given flag.
+ */
+ setStyleFlag: function(style, key, flag, value)
+ {
+ if (style == null || style.length == 0)
+ {
+ if (value || value == null)
+ {
+ style = key+'='+flag;
+ }
+ else
+ {
+ style = key+'=0';
+ }
+ }
+ else
+ {
+ var index = style.indexOf(key+'=');
+
+ if (index < 0)
+ {
+ var sep = (style.charAt(style.length-1) == ';') ? '' : ';';
+
+ if (value || value == null)
+ {
+ style = style + sep + key + '=' + flag;
+ }
+ else
+ {
+ style = style + sep + key + '=0';
+ }
+ }
+ else
+ {
+ var cont = style.indexOf(';', index);
+ var tmp = '';
+
+ if (cont < 0)
+ {
+ tmp = style.substring(index+key.length+1);
+ }
+ else
+ {
+ tmp = style.substring(index+key.length+1, cont);
+ }
+
+ if (value == null)
+ {
+ tmp = parseInt(tmp) ^ flag;
+ }
+ else if (value)
+ {
+ tmp = parseInt(tmp) | flag;
+ }
+ else
+ {
+ tmp = parseInt(tmp) & ~flag;
+ }
+
+ style = style.substring(0, index) + key + '=' + tmp +
+ ((cont >= 0) ? style.substring(cont) : '');
+ }
+ }
+
+ return style;
+ },
+
+ /**
+ * Function: getSizeForString
+ *
+ * Returns an <mxRectangle> with the size (width and height in pixels) of
+ * the given string. The string may contain HTML markup. Newlines should be
+ * converted to <br> before calling this method.
+ *
+ * Example:
+ *
+ * (code)
+ * var label = graph.getLabel(cell).replace(/\n/g, "<br>");
+ * var size = graph.getSizeForString(label);
+ * (end)
+ *
+ * Parameters:
+ *
+ * text - String whose size should be returned.
+ * fontSize - Integer that specifies the font size in pixels. Default is
+ * <mxConstants.DEFAULT_FONTSIZE>.
+ * fontFamily - String that specifies the name of the font family. Default
+ * is <mxConstants.DEFAULT_FONTFAMILY>.
+ */
+ getSizeForString: function(text, fontSize, fontFamily)
+ {
+ var div = document.createElement('div');
+
+ // Sets the font size and family if non-default
+ div.style.fontSize = (fontSize || mxConstants.DEFAULT_FONTSIZE) + 'px';
+ div.style.fontFamily = fontFamily || mxConstants.DEFAULT_FONTFAMILY;
+
+ // Disables block layout and outside wrapping and hides the div
+ div.style.position = 'absolute';
+ div.style.display = 'inline';
+ div.style.visibility = 'hidden';
+
+ // Adds the text and inserts into DOM for updating of size
+ div.innerHTML = text;
+ document.body.appendChild(div);
+
+ // Gets the size and removes from DOM
+ var size = new mxRectangle(0, 0, div.offsetWidth, div.offsetHeight);
+ document.body.removeChild(div);
+
+ return size;
+ },
+
+ /**
+ * Function: getViewXml
+ */
+ getViewXml: function(graph, scale, cells, x0, y0)
+ {
+ x0 = (x0 != null) ? x0 : 0;
+ y0 = (y0 != null) ? y0 : 0;
+ scale = (scale != null) ? scale : 1;
+
+ if (cells == null)
+ {
+ var model = graph.getModel();
+ cells = [model.getRoot()];
+ }
+
+ var view = graph.getView();
+ var result = null;
+
+ // Disables events on the view
+ var eventsEnabled = view.isEventsEnabled();
+ view.setEventsEnabled(false);
+
+ // Workaround for label bounds not taken into account for image export.
+ // Creates a temporary draw pane which is used for rendering the text.
+ // Text rendering is required for finding the bounds of the labels.
+ var drawPane = view.drawPane;
+ var overlayPane = view.overlayPane;
+
+ if (graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ view.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ view.canvas.appendChild(view.drawPane);
+
+ // Redirects cell overlays into temporary container
+ view.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ view.canvas.appendChild(view.overlayPane);
+ }
+ else
+ {
+ view.drawPane = view.drawPane.cloneNode(false);
+ view.canvas.appendChild(view.drawPane);
+
+ // Redirects cell overlays into temporary container
+ view.overlayPane = view.overlayPane.cloneNode(false);
+ view.canvas.appendChild(view.overlayPane);
+ }
+
+ // Resets the translation
+ var translate = view.getTranslate();
+ view.translate = new mxPoint(x0, y0);
+
+ // Creates the temporary cell states in the view
+ var temp = new mxTemporaryCellStates(graph.getView(), scale, cells);
+
+ try
+ {
+ var enc = new mxCodec();
+ result = enc.encode(graph.getView());
+ }
+ finally
+ {
+ temp.destroy();
+ view.translate = translate;
+ view.canvas.removeChild(view.drawPane);
+ view.canvas.removeChild(view.overlayPane);
+ view.drawPane = drawPane;
+ view.overlayPane = overlayPane;
+ view.setEventsEnabled(eventsEnabled);
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: getScaleForPageCount
+ *
+ * Returns the scale to be used for printing the graph with the given
+ * bounds across the specifies number of pages with the given format. The
+ * scale is always computed such that it given the given amount or fewer
+ * pages in the print output. See <mxPrintPreview> for an example.
+ *
+ * Parameters:
+ *
+ * pageCount - Specifies the number of pages in the print output.
+ * graph - <mxGraph> that should be printed.
+ * pageFormat - Optional <mxRectangle> that specifies the page format.
+ * Default is <mxConstants.PAGE_FORMAT_A4_PORTRAIT>.
+ * border - The border along each side of every page.
+ */
+ getScaleForPageCount: function(pageCount, graph, pageFormat, border)
+ {
+ if (pageCount < 1)
+ {
+ // We can't work with less than 1 page, return no scale
+ // change
+ return 1;
+ }
+
+ pageFormat = (pageFormat != null) ? pageFormat : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+ border = (border != null) ? border : 0;
+
+ var availablePageWidth = pageFormat.width - (border * 2);
+ var availablePageHeight = pageFormat.height - (border * 2);
+
+ // Work out the number of pages required if the
+ // graph is not scaled.
+ var graphBounds = graph.getGraphBounds().clone();
+ var sc = graph.getView().getScale();
+ graphBounds.width /= sc;
+ graphBounds.height /= sc;
+ var graphWidth = graphBounds.width;
+ var graphHeight = graphBounds.height;
+
+ var scale = 1;
+
+ // The ratio of the width/height for each printer page
+ var pageFormatAspectRatio = availablePageWidth / availablePageHeight;
+ // The ratio of the width/height for the graph to be printer
+ var graphAspectRatio = graphWidth / graphHeight;
+
+ // The ratio of horizontal pages / vertical pages for this
+ // graph to maintain its aspect ratio on this page format
+ var pagesAspectRatio = graphAspectRatio / pageFormatAspectRatio;
+
+ // Factor the square root of the page count up and down
+ // by the pages aspect ratio to obtain a horizontal and
+ // vertical page count that adds up to the page count
+ // and has the correct aspect ratio
+ var pageRoot = Math.sqrt(pageCount);
+ var pagesAspectRatioSqrt = Math.sqrt(pagesAspectRatio);
+ var numRowPages = pageRoot * pagesAspectRatioSqrt;
+ var numColumnPages = pageRoot / pagesAspectRatioSqrt;
+
+ // These value are rarely more than 2 rounding downs away from
+ // a total that meets the page count. In cases of one being less
+ // than 1 page, the other value can be too high and take more iterations
+ // In this case, just change that value to be the page count, since
+ // we know the other value is 1
+ if (numRowPages < 1 && numColumnPages > pageCount)
+ {
+ var scaleChange = numColumnPages / pageCount;
+ numColumnPages = pageCount;
+ numRowPages /= scaleChange;
+ }
+
+ if (numColumnPages < 1 && numRowPages > pageCount)
+ {
+ var scaleChange = numRowPages / pageCount;
+ numRowPages = pageCount;
+ numColumnPages /= scaleChange;
+ }
+
+ var currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);
+
+ var numLoops = 0;
+
+ // Iterate through while the rounded up number of pages comes to
+ // a total greater than the required number
+ while (currentTotalPages > pageCount)
+ {
+ // Round down the page count (rows or columns) that is
+ // closest to its next integer down in percentage terms.
+ // i.e. Reduce the page total by reducing the total
+ // page area by the least possible amount
+
+ var roundRowDownProportion = Math.floor(numRowPages) / numRowPages;
+ var roundColumnDownProportion = Math.floor(numColumnPages) / numColumnPages;
+
+ // If the round down proportion is, work out the proportion to
+ // round down to 1 page less
+ if (roundRowDownProportion == 1)
+ {
+ roundRowDownProportion = Math.floor(numRowPages-1) / numRowPages;
+ }
+ if (roundColumnDownProportion == 1)
+ {
+ roundColumnDownProportion = Math.floor(numColumnPages-1) / numColumnPages;
+ }
+
+ // Check which rounding down is smaller, but in the case of very small roundings
+ // try the other dimension instead
+ var scaleChange = 1;
+
+ // Use the higher of the two values
+ if (roundRowDownProportion > roundColumnDownProportion)
+ {
+ scaleChange = roundRowDownProportion;
+ }
+ else
+ {
+ scaleChange = roundColumnDownProportion;
+ }
+
+ numRowPages = numRowPages * scaleChange;
+ numColumnPages = numColumnPages * scaleChange;
+ currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);
+
+ numLoops++;
+
+ if (numLoops > 10)
+ {
+ break;
+ }
+ }
+
+ // Work out the scale from the number of row pages required
+ // The column pages will give the same value
+ var posterWidth = availablePageWidth * numRowPages;
+ scale = posterWidth / graphWidth;
+
+ // Allow for rounding errors
+ return scale * 0.99999;
+ },
+
+ /**
+ * Function: show
+ *
+ * Copies the styles and the markup from the graph's container into the
+ * given document and removes all cursor styles. The document is returned.
+ *
+ * This function should be called from within the document with the graph.
+ * If you experience problems with missing stylesheets in IE then try adding
+ * the domain to the trusted sites.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to be copied.
+ * doc - Document where the new graph is created.
+ * x0 - X-coordinate of the graph view origin. Default is 0.
+ * y0 - Y-coordinate of the graph view origin. Default is 0.
+ */
+ show: function(graph, doc, x0, y0)
+ {
+ x0 = (x0 != null) ? x0 : 0;
+ y0 = (y0 != null) ? y0 : 0;
+
+ if (doc == null)
+ {
+ var wnd = window.open();
+ doc = wnd.document;
+ }
+ else
+ {
+ doc.open();
+ }
+
+ var bounds = graph.getGraphBounds();
+ var dx = -bounds.x + x0;
+ var dy = -bounds.y + y0;
+
+ // 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)
+ {
+ var html = '<html>';
+ html += '<head>';
+
+ var base = document.getElementsByTagName('base');
+
+ for (var i = 0; i < base.length; i++)
+ {
+ html += base[i].outerHTML;
+ }
+
+ html += '<style>';
+
+ // Copies the stylesheets without having to load them again
+ for (var i = 0; i < document.styleSheets.length; i++)
+ {
+ try
+ {
+ html += document.styleSheets(i).cssText;
+ }
+ catch (e)
+ {
+ // ignore security exception
+ }
+ }
+
+ html += '</style>';
+
+ html += '</head>';
+ html += '<body>';
+
+ // Copies the contents of the graph container
+ html += graph.container.innerHTML;
+
+ html += '</body>';
+ html += '<html>';
+
+ doc.writeln(html);
+ doc.close();
+
+ // Makes sure the inner container is on the top, left
+ var node = doc.body.getElementsByTagName('DIV')[0];
+
+ if (node != null)
+ {
+ node.style.position = 'absolute';
+ node.style.left = dx + 'px';
+ node.style.top = dy + 'px';
+ }
+ }
+ else
+ {
+ doc.writeln('<html');
+ doc.writeln('<head>');
+
+ var base = document.getElementsByTagName('base');
+
+ for (var i=0; i<base.length; i++)
+ {
+ doc.writeln(mxUtils.getOuterHtml(base[i]));
+ }
+
+ var links = document.getElementsByTagName('link');
+
+ for (var i=0; i<links.length; i++)
+ {
+ doc.writeln(mxUtils.getOuterHtml(links[i]));
+ }
+
+ var styles = document.getElementsByTagName('style');
+
+ for (var i=0; i<styles.length; i++)
+ {
+ doc.writeln(mxUtils.getOuterHtml(styles[i]));
+ }
+
+ doc.writeln('</head>');
+ doc.writeln('</html>');
+ doc.close();
+
+ // Workaround for FF2 which has no body element in a document where
+ // the body has been added using document.write.
+ if (doc.body == null)
+ {
+ doc.documentElement.appendChild(doc.createElement('body'));
+ }
+
+ // Workaround for missing scrollbars in FF
+ doc.body.style.overflow = 'auto';
+
+ var node = graph.container.firstChild;
+
+ while (node != null)
+ {
+ var clone = node.cloneNode(true);
+ doc.body.appendChild(clone);
+ node = node.nextSibling;
+ }
+
+ // Shifts negative coordinates into visible space
+ var node = doc.getElementsByTagName('g')[0];
+
+ if (node != null)
+ {
+ node.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+
+ // Updates the size of the SVG container
+ var root = node.ownerSVGElement;
+ root.setAttribute('width', bounds.width + Math.max(bounds.x, 0) + 3);
+ root.setAttribute('height', bounds.height + Math.max(bounds.y, 0) + 3);
+ }
+ }
+
+ mxUtils.removeCursors(doc.body);
+
+ return doc;
+ },
+
+ /**
+ * Function: printScreen
+ *
+ * Prints the specified graph using a new window and the built-in print
+ * dialog.
+ *
+ * This function should be called from within the document with the graph.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to be printed.
+ */
+ printScreen: function(graph)
+ {
+ var wnd = window.open();
+ mxUtils.show(graph, wnd.document);
+
+ var print = function()
+ {
+ wnd.focus();
+ wnd.print();
+ wnd.close();
+ };
+
+ // Workaround for Google Chrome which needs a bit of a
+ // delay in order to render the SVG contents
+ if (mxClient.IS_GC)
+ {
+ wnd.setTimeout(print, 500);
+ }
+ else
+ {
+ print();
+ }
+ },
+
+ /**
+ * Function: popup
+ *
+ * Shows the specified text content in a new <mxWindow> or a new browser
+ * window if isInternalWindow is false.
+ *
+ * Parameters:
+ *
+ * content - String that specifies the text to be displayed.
+ * isInternalWindow - Optional boolean indicating if an mxWindow should be
+ * used instead of a new browser window. Default is false.
+ */
+ popup: function(content, isInternalWindow)
+ {
+ if (isInternalWindow)
+ {
+ var div = document.createElement('div');
+
+ div.style.overflow = 'scroll';
+ div.style.width = '636px';
+ div.style.height = '460px';
+
+ var pre = document.createElement('pre');
+ pre.innerHTML = mxUtils.htmlEntities(content, false).
+ replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+
+ div.appendChild(pre);
+
+ var w = document.body.clientWidth;
+ var h = (document.body.clientHeight || document.documentElement.clientHeight);
+ var wnd = new mxWindow('Popup Window', div,
+ w/2-320, h/2-240, 640, 480, false, true);
+
+ wnd.setClosable(true);
+ wnd.setVisible(true);
+ }
+ else
+ {
+ // Wraps up the XML content in a textarea
+ if (mxClient.IS_NS)
+ {
+ var wnd = window.open();
+ wnd.document.writeln('<pre>'+mxUtils.htmlEntities(content)+'</pre');
+ wnd.document.close();
+ }
+ else
+ {
+ var wnd = window.open();
+ var pre = wnd.document.createElement('pre');
+ pre.innerHTML = mxUtils.htmlEntities(content, false).
+ replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+ wnd.document.body.appendChild(pre);
+ }
+ }
+ },
+
+ /**
+ * Function: alert
+ *
+ * Displayss the given alert in a new dialog. This implementation uses the
+ * built-in alert function. This is used to display validation errors when
+ * connections cannot be changed or created.
+ *
+ * Parameters:
+ *
+ * message - String specifying the message to be displayed.
+ */
+ alert: function(message)
+ {
+ alert(message);
+ },
+
+ /**
+ * Function: prompt
+ *
+ * Displays the given message in a prompt dialog. This implementation uses
+ * the built-in prompt function.
+ *
+ * Parameters:
+ *
+ * message - String specifying the message to be displayed.
+ * defaultValue - Optional string specifying the default value.
+ */
+ prompt: function(message, defaultValue)
+ {
+ return prompt(message, defaultValue);
+ },
+
+ /**
+ * Function: confirm
+ *
+ * Displays the given message in a confirm dialog. This implementation uses
+ * the built-in confirm function.
+ *
+ * Parameters:
+ *
+ * message - String specifying the message to be displayed.
+ */
+ confirm: function(message)
+ {
+ return confirm(message);
+ },
+
+ /**
+ * Function: error
+ *
+ * Displays the given error message in a new <mxWindow> of the given width.
+ * If close is true then an additional close button is added to the window.
+ * The optional icon specifies the icon to be used for the window. Default
+ * is <mxUtils.errorImage>.
+ *
+ * Parameters:
+ *
+ * message - String specifying the message to be displayed.
+ * width - Integer specifying the width of the window.
+ * close - Optional boolean indicating whether to add a close button.
+ * icon - Optional icon for the window decoration.
+ */
+ error: function(message, width, close, icon)
+ {
+ var div = document.createElement('div');
+ div.style.padding = '20px';
+
+ var img = document.createElement('img');
+ img.setAttribute('src', icon || mxUtils.errorImage);
+ img.setAttribute('valign', 'bottom');
+ img.style.verticalAlign = 'middle';
+ div.appendChild(img);
+
+ div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+ div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+ div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+ mxUtils.write(div, message);
+
+ var w = document.body.clientWidth;
+ var h = (document.body.clientHeight || document.documentElement.clientHeight);
+ var warn = new mxWindow(mxResources.get(mxUtils.errorResource) ||
+ mxUtils.errorResource, div, (w-width)/2, h/4, width, null,
+ false, true);
+
+ if (close)
+ {
+ mxUtils.br(div);
+
+ var tmp = document.createElement('p');
+ var button = document.createElement('button');
+
+ if (mxClient.IS_IE)
+ {
+ button.style.cssText = 'float:right';
+ }
+ else
+ {
+ button.setAttribute('style', 'float:right');
+ }
+
+ mxEvent.addListener(button, 'click', function(evt)
+ {
+ warn.destroy();
+ });
+
+ mxUtils.write(button, mxResources.get(mxUtils.closeResource) ||
+ mxUtils.closeResource);
+
+ tmp.appendChild(button);
+ div.appendChild(tmp);
+
+ mxUtils.br(div);
+
+ warn.setClosable(true);
+ }
+
+ warn.setVisible(true);
+
+ return warn;
+ },
+
+ /**
+ * Function: makeDraggable
+ *
+ * Configures the given DOM element to act as a drag source for the
+ * specified graph. Returns a a new <mxDragSource>. If
+ * <mxDragSource.guideEnabled> is enabled then the x and y arguments must
+ * be used in funct to match the preview location.
+ *
+ * Example:
+ *
+ * (code)
+ * var funct = function(graph, evt, cell, x, y)
+ * {
+ * if (graph.canImportCell(cell))
+ * {
+ * var parent = graph.getDefaultParent();
+ * var vertex = null;
+ *
+ * graph.getModel().beginUpdate();
+ * try
+ * {
+ * vertex = graph.insertVertex(parent, null, 'Hello', x, y, 80, 30);
+ * }
+ * finally
+ * {
+ * graph.getModel().endUpdate();
+ * }
+ *
+ * graph.setSelectionCell(vertex);
+ * }
+ * }
+ *
+ * var img = document.createElement('img');
+ * img.setAttribute('src', 'editors/images/rectangle.gif');
+ * img.style.position = 'absolute';
+ * img.style.left = '0px';
+ * img.style.top = '0px';
+ * img.style.width = '16px';
+ * img.style.height = '16px';
+ *
+ * var dragImage = img.cloneNode(true);
+ * dragImage.style.width = '32px';
+ * dragImage.style.height = '32px';
+ * mxUtils.makeDraggable(img, graph, funct, dragImage);
+ * document.body.appendChild(img);
+ * (end)
+ *
+ * Parameters:
+ *
+ * element - DOM element to make draggable.
+ * graphF - <mxGraph> that acts as the drop target or a function that takes a
+ * mouse event and returns the current <mxGraph>.
+ * funct - Function to execute on a successful drop.
+ * dragElement - Optional DOM node to be used for the drag preview.
+ * dx - Optional horizontal offset between the cursor and the drag
+ * preview.
+ * dy - Optional vertical offset between the cursor and the drag
+ * preview.
+ * autoscroll - Optional boolean that specifies if autoscroll should be
+ * used. Default is mxGraph.autoscroll.
+ * scalePreview - Optional boolean that specifies if the preview element
+ * should be scaled according to the graph scale. If this is true, then
+ * the offsets will also be scaled. Default is false.
+ * highlightDropTargets - Optional boolean that specifies if dropTargets
+ * should be highlighted. Default is true.
+ * getDropTarget - Optional function to return the drop target for a given
+ * location (x, y). Default is mxGraph.getCellAt.
+ */
+ makeDraggable: function(element, graphF, funct, dragElement, dx, dy, autoscroll,
+ scalePreview, highlightDropTargets, getDropTarget)
+ {
+ var dragSource = new mxDragSource(element, funct);
+ dragSource.dragOffset = new mxPoint((dx != null) ? dx : 0,
+ (dy != null) ? dy : mxConstants.TOOLTIP_VERTICAL_OFFSET);
+ dragSource.autoscroll = autoscroll;
+
+ // Cannot enable this by default. This needs to be enabled in the caller
+ // if the funct argument uses the new x- and y-arguments.
+ dragSource.setGuidesEnabled(false);
+
+ if (highlightDropTargets != null)
+ {
+ dragSource.highlightDropTargets = highlightDropTargets;
+ }
+
+ // Overrides function to find drop target cell
+ if (getDropTarget != null)
+ {
+ dragSource.getDropTarget = getDropTarget;
+ }
+
+ // Overrides function to get current graph
+ dragSource.getGraphForEvent = function(evt)
+ {
+ return (typeof(graphF) == 'function') ? graphF(evt) : graphF;
+ };
+
+ // Translates switches into dragSource customizations
+ if (dragElement != null)
+ {
+ dragSource.createDragElement = function()
+ {
+ return dragElement.cloneNode(true);
+ };
+
+ if (scalePreview)
+ {
+ dragSource.createPreviewElement = function(graph)
+ {
+ var elt = dragElement.cloneNode(true);
+
+ var w = parseInt(elt.style.width);
+ var h = parseInt(elt.style.height);
+ elt.style.width = Math.round(w * graph.view.scale) + 'px';
+ elt.style.height = Math.round(h * graph.view.scale) + 'px';
+
+ return elt;
+ };
+ }
+ }
+
+ return dragSource;
+ }
+
+};
diff --git a/src/js/util/mxWindow.js b/src/js/util/mxWindow.js
new file mode 100644
index 0000000..e4cbcfc
--- /dev/null
+++ b/src/js/util/mxWindow.js
@@ -0,0 +1,1065 @@
+/**
+ * $Id: mxWindow.js,v 1.67 2012-10-11 17:18:51 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * 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)
+ *
+ * Event: mxEvent.MOVE_START
+ *
+ * Fires before the window is moved. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE
+ *
+ * Fires while the window is being moved. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE_END
+ *
+ * Fires after the window is moved. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_START
+ *
+ * Fires before the window is resized. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE
+ *
+ * Fires while the window is being resized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_END
+ *
+ * Fires after the window is resized. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MAXIMIZE
+ *
+ * Fires after the window is maximized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.MINIMIZE
+ *
+ * Fires after the window is minimized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.NORMALIZE
+ *
+ * Fires after the window is normalized, that is, it returned from
+ * maximized or minimized state. The <code>event</code> property contains the
+ * corresponding mouse event.
+ *
+ * Event: mxEvent.ACTIVATE
+ *
+ * Fires after a window is activated. The <code>previousWindow</code> property
+ * contains the previous window. The event sender is the active window.
+ *
+ * Event: mxEvent.SHOW
+ *
+ * Fires after the window is shown. This event has no properties.
+ *
+ * Event: mxEvent.HIDE
+ *
+ * Fires after the window is hidden. This event has no properties.
+ *
+ * Event: mxEvent.CLOSE
+ *
+ * Fires before the window is closed. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.DESTROY
+ *
+ * Fires before the window is destroyed. This event has no properties.
+ *
+ * Constructor: mxWindow
+ *
+ * Constructs a new window with the given dimension and title to display
+ * the specified content. The window elements use the given style as a
+ * prefix for the classnames of the respective window elements, namely,
+ * the window title and window pane. The respective postfixes are appended
+ * to the given stylename as follows:
+ *
+ * style - Base style for the window.
+ * style+Title - Style for the window title.
+ * style+Pane - Style for the window pane.
+ *
+ * The default value for style is mxWindow, resulting in the following
+ * classnames for the window elements: mxWindow, mxWindowTitle and
+ * mxWindowPane.
+ *
+ * If replaceNode is given then the window replaces the given DOM node in
+ * the document.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title of the new window.
+ * content - DOM node that is used as the window content.
+ * x - X-coordinate of the window location.
+ * y - Y-coordinate of the window location.
+ * width - Width of the window.
+ * height - Optional height of the window. Default is to match the height
+ * of the content at the specified width.
+ * minimizable - Optional boolean indicating if the window is minimizable.
+ * Default is true.
+ * movable - Optional boolean indicating if the window is movable. Default
+ * is true.
+ * replaceNode - Optional DOM node that the window should replace.
+ * style - Optional base classname for the window elements. Default is
+ * mxWindow.
+ */
+function mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style)
+{
+ if (content != null)
+ {
+ minimizable = (minimizable != null) ? minimizable : true;
+ this.content = content;
+ this.init(x, y, width, height, style);
+
+ this.installMaximizeHandler();
+ this.installMinimizeHandler();
+ this.installCloseHandler();
+ this.setMinimizable(minimizable);
+ this.setTitle(title);
+
+ if (movable == null || movable)
+ {
+ this.installMoveHandler();
+ }
+
+ if (replaceNode != null && replaceNode.parentNode != null)
+ {
+ replaceNode.parentNode.replaceChild(this.div, replaceNode);
+ }
+ else
+ {
+ 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: content
+ *
+ * Reference to the DOM node that represents the window content.
+ */
+mxWindow.prototype.content = false;
+
+/**
+ * Variable: minimumSize
+ *
+ * <mxRectangle> that specifies the minimum width and height of the window.
+ * Default is (50, 40).
+ */
+mxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);
+
+/**
+ * Variable: title
+ *
+ * Reference to the DOM node (TD) that contains the title.
+ */
+mxWindow.prototype.title = false;
+
+/**
+ * Variable: content
+ *
+ * Reference to the DOM node that represents the window content.
+ */
+mxWindow.prototype.content = false;
+
+/**
+ * Variable: destroyOnClose
+ *
+ * Specifies if the window should be destroyed when it is closed. If this
+ * is false then the window is hidden using <setVisible>. Default is true.
+ */
+mxWindow.prototype.destroyOnClose = true;
+
+/**
+ * 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;
+
+ // Workaround for table size problems in FF
+ if (width != null)
+ {
+ if (!mxClient.IS_IE)
+ {
+ this.div.style.width = width+'px';
+ }
+
+ this.table.style.width = width+'px';
+ }
+
+ if (height != null)
+ {
+ if (!mxClient.IS_IE)
+ {
+ 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';
+ 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';
+
+ 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_IE || 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();
+ });
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ mxEvent.addListener(this.title, md, activator);
+ mxEvent.addListener(this.table, md, 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 || '');
+};
+
+/**
+ * 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.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.
+ */
+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', mxClient.imageBasePath + '/resize.gif');
+ this.resize.style.cursor = 'nw-resize';
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(this.resize, md, mxUtils.bind(this, function(evt)
+ {
+ this.activate();
+ var startX = mxEvent.getClientX(evt);
+ var startY = mxEvent.getClientY(evt);
+ var width = this.div.offsetWidth;
+ var height = this.div.offsetHeight;
+
+ // 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.setSize(width + dx, height + dy);
+
+ this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt));
+ mxEvent.consume(evt);
+ });
+
+ var dropHandler = mxUtils.bind(this, function(evt)
+ {
+ mxEvent.removeListener(document, mm, dragHandler);
+ mxEvent.removeListener(document, mu, dropHandler);
+
+ this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt));
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(document, mm, dragHandler);
+ mxEvent.addListener(document, mu, dropHandler);
+
+ this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt));
+ mxEvent.consume(evt);
+ }));
+
+ 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_IE)
+ {
+ 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_IE)
+ {
+ this.contentWrapper.style.height =
+ (this.div.offsetHeight - this.title.offsetHeight - 2)+'px';
+ }
+};
+
+/**
+ * Function: setMinimizable
+ *
+ * Sets if the window is minimizable.
+ */
+mxWindow.prototype.setMinimizable = function(minimizable)
+{
+ this.minimize.style.display = (minimizable) ? '' : 'none';
+};
+
+/**
+ * Function: getMinimumSize
+ *
+ * Returns an <mxRectangle> that specifies the size for the minimized window.
+ * A width or height of 0 means keep the existing width or height. This
+ * implementation returns the height of the window title and keeps the width.
+ */
+mxWindow.prototype.getMinimumSize = function()
+{
+ return new mxRectangle(0, 0, 0, this.title.offsetHeight);
+};
+
+/**
+ * Function: installMinimizeHandler
+ *
+ * Installs the event listeners required for minimizing the window.
+ */
+mxWindow.prototype.installMinimizeHandler = function()
+{
+ this.minimize = document.createElement('img');
+
+ this.minimize.setAttribute('src', this.minimizeImage);
+ this.minimize.setAttribute('align', 'right');
+ this.minimize.setAttribute('title', 'Minimize');
+ this.minimize.style.cursor = 'pointer';
+ this.minimize.style.marginRight = '1px';
+ this.minimize.style.display = 'none';
+
+ this.title.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_IE)
+ {
+ this.div.style.height = minSize.height + 'px';
+ }
+
+ this.table.style.height = minSize.height + 'px';
+ }
+
+ if (minSize.width > 0)
+ {
+ if (!mxClient.IS_IE)
+ {
+ 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_IE)
+ {
+ 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);
+ });
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ mxEvent.addListener(this.minimize, md, 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('align', 'right');
+ this.maximize.setAttribute('title', 'Maximize');
+ this.maximize.style.cursor = 'default';
+ this.maximize.style.marginLeft = '1px';
+ this.maximize.style.cursor = 'pointer';
+ this.maximize.style.display = 'none';
+
+ this.title.appendChild(this.maximize);
+
+ var maximized = false;
+ var x = null;
+ var y = null;
+ var height = null;
+ var width = 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 = '';
+ this.minimize.style.visibility = 'hidden';
+
+ // 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';
+
+ if (!mxClient.IS_IE)
+ {
+ this.div.style.height = (document.body.clientHeight-2)+'px';
+ this.div.style.width = (document.body.clientWidth-2)+'px';
+ }
+
+ this.table.style.width = (document.body.clientWidth-2)+'px';
+ this.table.style.height = (document.body.clientHeight-2)+'px';
+
+ if (this.resize != null)
+ {
+ this.resize.style.visibility = 'hidden';
+ }
+
+ if (!mxClient.IS_IE)
+ {
+ var style = mxUtils.getCurrentStyle(this.contentWrapper);
+
+ if (style.overflow == 'auto' || this.resize != null)
+ {
+ this.contentWrapper.style.height =
+ (this.div.offsetHeight - this.title.offsetHeight - 2)+'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.visibility = '';
+
+ // Restores window state
+ this.div.style.left = x+'px';
+ this.div.style.top = y+'px';
+
+ if (!mxClient.IS_IE)
+ {
+ 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 - 2)+'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);
+ }
+ });
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ mxEvent.addListener(this.maximize, md, 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';
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(this.title, md, 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.removeListener(document, mm, dragHandler);
+ mxEvent.removeListener(document, mu, dropHandler);
+
+ this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt));
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(document, mm, dragHandler);
+ mxEvent.addListener(document, mu, dropHandler);
+
+ this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt));
+ mxEvent.consume(evt);
+ }));
+};
+
+/**
+ * Function: setLocation
+ *
+ * Sets the upper, left corner of the window.
+ */
+ mxWindow.prototype.setLocation = function(x, y)
+ {
+ this.div.style.left = x + 'px';
+ this.div.style.top = y + 'px';
+ };
+
+/**
+ * Function: getX
+ *
+ * Returns the current position on the x-axis.
+ */
+mxWindow.prototype.getX = function()
+{
+ return parseInt(this.div.style.left);
+};
+
+/**
+ * Function: getY
+ *
+ * Returns the current position on the y-axis.
+ */
+mxWindow.prototype.getY = function()
+{
+ return parseInt(this.div.style.top);
+};
+
+/**
+ * Function: installCloseHandler
+ *
+ * Adds the <closeImage> as a new image node in <closeImg> and installs the
+ * <close> event.
+ */
+mxWindow.prototype.installCloseHandler = function()
+{
+ this.closeImg = document.createElement('img');
+
+ this.closeImg.setAttribute('src', this.closeImage);
+ this.closeImg.setAttribute('align', 'right');
+ this.closeImg.setAttribute('title', 'Close');
+ this.closeImg.style.marginLeft = '2px';
+ this.closeImg.style.cursor = 'pointer';
+ this.closeImg.style.display = 'none';
+
+ this.title.insertBefore(this.closeImg, this.title.firstChild);
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ mxEvent.addListener(this.closeImg, md, 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.visibility != 'hidden';
+ }
+
+ 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.visibility = '';
+ this.activate();
+
+ var style = mxUtils.getCurrentStyle(this.contentWrapper);
+
+ if (!mxClient.IS_IE && (style.overflow == 'auto' || this.resize != null))
+ {
+ this.contentWrapper.style.height =
+ (this.div.offsetHeight - this.title.offsetHeight - 2)+'px';
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SHOW));
+};
+
+/**
+ * Function: hide
+ *
+ * Hides the window.
+ */
+mxWindow.prototype.hide = function()
+{
+ this.div.style.visibility = 'hidden';
+ this.fireEvent(new mxEventObject(mxEvent.HIDE));
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the window and removes all associated resources. Fires a
+ * <destroy> event prior to destroying the window.
+ */
+mxWindow.prototype.destroy = function()
+{
+ this.fireEvent(new mxEventObject(mxEvent.DESTROY));
+
+ if (this.div != null)
+ {
+ mxEvent.release(this.div);
+ this.div.parentNode.removeChild(this.div);
+ this.div = null;
+ }
+
+ this.title = null;
+ this.content = null;
+ this.contentWrapper = null;
+};
diff --git a/src/js/util/mxXmlCanvas2D.js b/src/js/util/mxXmlCanvas2D.js
new file mode 100644
index 0000000..499c71a
--- /dev/null
+++ b/src/js/util/mxXmlCanvas2D.js
@@ -0,0 +1,715 @@
+/**
+ * $Id: mxXmlCanvas2D.js,v 1.9 2012-04-24 13:56:56 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxXmlCanvas2D
+ *
+ * Implements a canvas to be used with <mxImageExport>. This canvas writes all
+ * calls as child nodes to the given root XML node.
+ *
+ * (code)
+ * var xmlDoc = mxUtils.createXmlDocument();
+ * var root = xmlDoc.createElement('output');
+ * xmlDoc.appendChild(root);
+ * var xmlCanvas = new mxXmlCanvas2D(root);
+ * (end)
+ *
+ * Constructor: mxXmlCanvas2D
+ *
+ * Constructs a XML canvas.
+ *
+ * Parameters:
+ *
+ * root - XML node for adding child nodes.
+ */
+var mxXmlCanvas2D = function(root)
+{
+ /**
+ * Variable: converter
+ *
+ * Holds the <mxUrlConverter> to convert image URLs.
+ */
+ var converter = new mxUrlConverter();
+
+ /**
+ * Variable: compressed
+ *
+ * Specifies if the output should be compressed by removing redundant calls.
+ * Default is true.
+ */
+ var compressed = true;
+
+ /**
+ * Variable: textEnabled
+ *
+ * Specifies if text output should be enabled. Default is true.
+ */
+ var textEnabled = true;
+
+ // Private reference to the owner document
+ var doc = root.ownerDocument;
+
+ // Implements stack for save/restore
+ var stack = [];
+
+ // Implements state for redundancy checks
+ var state =
+ {
+ alpha: 1,
+ dashed: false,
+ strokewidth: 1,
+ fontsize: mxConstants.DEFAULT_FONTSIZE,
+ fontfamily: mxConstants.DEFAULT_FONTFAMILY,
+ fontcolor: '#000000'
+ };
+
+ // Private helper function set set precision to 2
+ var f2 = function(x)
+ {
+ return Math.round(parseFloat(x) * 100) / 100;
+ };
+
+ // Returns public interface
+ return {
+
+ /**
+ * Function: getConverter
+ *
+ * Returns <converter>.
+ */
+ getConverter: function()
+ {
+ return converter;
+ },
+
+ /**
+ * Function: isCompressed
+ *
+ * Returns <compressed>.
+ */
+ isCompressed: function()
+ {
+ return compressed;
+ },
+
+ /**
+ * Function: setCompressed
+ *
+ * Sets <compressed>.
+ */
+ setCompressed: function(value)
+ {
+ compressed = value;
+ },
+
+ /**
+ * Function: isTextEnabled
+ *
+ * Returns <textEnabled>.
+ */
+ isTextEnabled: function()
+ {
+ return textEnabled;
+ },
+
+ /**
+ * Function: setTextEnabled
+ *
+ * Sets <textEnabled>.
+ */
+ setTextEnabled: function(value)
+ {
+ textEnabled = value;
+ },
+
+ /**
+ * Function: getDocument
+ *
+ * Returns the owner document of the root element.
+ */
+ getDocument: function()
+ {
+ return doc;
+ },
+
+ /**
+ * Function: save
+ *
+ * Saves the state of the graphics object.
+ */
+ save: function()
+ {
+ if (compressed)
+ {
+ stack.push(state);
+ state = mxUtils.clone(state);
+ }
+
+ root.appendChild(doc.createElement('save'));
+ },
+
+ /**
+ * Function: restore
+ *
+ * Restores the state of the graphics object.
+ */
+ restore: function()
+ {
+ if (compressed)
+ {
+ state = stack.pop();
+ }
+
+ root.appendChild(doc.createElement('restore'));
+ },
+
+ /**
+ * Function: scale
+ *
+ * Scales the current graphics object.
+ */
+ scale: function(value)
+ {
+ var elem = doc.createElement('scale');
+ elem.setAttribute('scale', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: translate
+ *
+ * Translates the current graphics object.
+ */
+ translate: function(dx, dy)
+ {
+ var elem = doc.createElement('translate');
+ elem.setAttribute('dx', f2(dx));
+ elem.setAttribute('dy', f2(dy));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: rotate
+ *
+ * Rotates and/or flips the current graphics object.
+ */
+ rotate: function(theta, flipH, flipV, cx, cy)
+ {
+ var elem = doc.createElement('rotate');
+ elem.setAttribute('theta', f2(theta));
+ elem.setAttribute('flipH', (flipH) ? '1' : '0');
+ elem.setAttribute('flipV', (flipV) ? '1' : '0');
+ elem.setAttribute('cx', f2(cx));
+ elem.setAttribute('cy', f2(cy));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setStrokeWidth
+ *
+ * Sets the stroke width.
+ */
+ setStrokeWidth: function(value)
+ {
+ if (compressed)
+ {
+ if (state.strokewidth == value)
+ {
+ return;
+ }
+
+ state.strokewidth = value;
+ }
+
+ var elem = doc.createElement('strokewidth');
+ elem.setAttribute('width', f2(value));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setStrokeColor
+ *
+ * Sets the stroke color.
+ */
+ setStrokeColor: function(value)
+ {
+ var elem = doc.createElement('strokecolor');
+ elem.setAttribute('color', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setDashed
+ *
+ * Sets the dashed state to true or false.
+ */
+ setDashed: function(value)
+ {
+ if (compressed)
+ {
+ if (state.dashed == value)
+ {
+ return;
+ }
+
+ state.dashed = value;
+ }
+
+ var elem = doc.createElement('dashed');
+ elem.setAttribute('dashed', (value) ? '1' : '0');
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setDashPattern
+ *
+ * Sets the dashed pattern to the given space separated list of numbers.
+ */
+ setDashPattern: function(value)
+ {
+ var elem = doc.createElement('dashpattern');
+ elem.setAttribute('pattern', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setLineCap
+ *
+ * Sets the linecap.
+ */
+ setLineCap: function(value)
+ {
+ var elem = doc.createElement('linecap');
+ elem.setAttribute('cap', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setLineJoin
+ *
+ * Sets the linejoin.
+ */
+ setLineJoin: function(value)
+ {
+ var elem = doc.createElement('linejoin');
+ elem.setAttribute('join', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setMiterLimit
+ *
+ * Sets the miterlimit.
+ */
+ setMiterLimit: function(value)
+ {
+ var elem = doc.createElement('miterlimit');
+ elem.setAttribute('limit', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setFontSize
+ *
+ * Sets the fontsize.
+ */
+ setFontSize: function(value)
+ {
+ if (textEnabled)
+ {
+ if (compressed)
+ {
+ if (state.fontsize == value)
+ {
+ return;
+ }
+
+ state.fontsize = value;
+ }
+
+ var elem = doc.createElement('fontsize');
+ elem.setAttribute('size', value);
+ root.appendChild(elem);
+ }
+ },
+
+ /**
+ * Function: setFontColor
+ *
+ * Sets the fontcolor.
+ */
+ setFontColor: function(value)
+ {
+ if (textEnabled)
+ {
+ if (compressed)
+ {
+ if (state.fontcolor == value)
+ {
+ return;
+ }
+
+ state.fontcolor = value;
+ }
+
+ var elem = doc.createElement('fontcolor');
+ elem.setAttribute('color', value);
+ root.appendChild(elem);
+ }
+ },
+
+ /**
+ * Function: setFontFamily
+ *
+ * Sets the fontfamily.
+ */
+ setFontFamily: function(value)
+ {
+ if (textEnabled)
+ {
+ if (compressed)
+ {
+ if (state.fontfamily == value)
+ {
+ return;
+ }
+
+ state.fontfamily = value;
+ }
+
+ var elem = doc.createElement('fontfamily');
+ elem.setAttribute('family', value);
+ root.appendChild(elem);
+ }
+ },
+
+ /**
+ * Function: setFontStyle
+ *
+ * Sets the fontstyle.
+ */
+ setFontStyle: function(value)
+ {
+ if (textEnabled)
+ {
+ var elem = doc.createElement('fontstyle');
+ elem.setAttribute('style', value);
+ root.appendChild(elem);
+ }
+ },
+
+ /**
+ * Function: setAlpha
+ *
+ * Sets the current alpha.
+ */
+ setAlpha: function(alpha)
+ {
+ if (compressed)
+ {
+ if (state.alpha == alpha)
+ {
+ return;
+ }
+
+ state.alpha = alpha;
+ }
+
+ var elem = doc.createElement('alpha');
+ elem.setAttribute('alpha', f2(alpha));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setFillColor
+ *
+ * Sets the fillcolor.
+ */
+ setFillColor: function(value)
+ {
+ var elem = doc.createElement('fillcolor');
+ elem.setAttribute('color', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setGradient
+ *
+ * Sets the gradient color.
+ */
+ setGradient: function(color1, color2, x, y, w, h, direction)
+ {
+ var elem = doc.createElement('gradient');
+ elem.setAttribute('c1', color1);
+ elem.setAttribute('c2', color2);
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+
+ // Default direction is south
+ if (direction != null)
+ {
+ elem.setAttribute('direction', direction);
+ }
+
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setGlassGradient
+ *
+ * Sets the glass gradient.
+ */
+ setGlassGradient: function(x, y, w, h)
+ {
+ var elem = doc.createElement('glass');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: rect
+ *
+ * Sets the current path to a rectangle.
+ */
+ rect: function(x, y, w, h)
+ {
+ var elem = doc.createElement('rect');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: roundrect
+ *
+ * Sets the current path to a rounded rectangle.
+ */
+ roundrect: function(x, y, w, h, dx, dy)
+ {
+ var elem = doc.createElement('roundrect');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ elem.setAttribute('dx', f2(dx));
+ elem.setAttribute('dy', f2(dy));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: ellipse
+ *
+ * Sets the current path to an ellipse.
+ */
+ ellipse: function(x, y, w, h)
+ {
+ var elem = doc.createElement('ellipse');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: image
+ *
+ * Paints an image.
+ */
+ image: function(x, y, w, h, src, aspect, flipH, flipV)
+ {
+ src = converter.convert(src);
+
+ // TODO: Add option for embedding images as base64
+ var elem = doc.createElement('image');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ elem.setAttribute('src', src);
+ elem.setAttribute('aspect', (aspect) ? '1' : '0');
+ elem.setAttribute('flipH', (flipH) ? '1' : '0');
+ elem.setAttribute('flipV', (flipV) ? '1' : '0');
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: text
+ *
+ * Paints the given text.
+ */
+ text: function(x, y, w, h, str, align, valign, vertical, wrap, format)
+ {
+ if (textEnabled)
+ {
+ var elem = doc.createElement('text');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ elem.setAttribute('str', str);
+
+ if (align != null)
+ {
+ elem.setAttribute('align', align);
+ }
+
+ if (valign != null)
+ {
+ elem.setAttribute('valign', valign);
+ }
+
+ elem.setAttribute('vertical', (vertical) ? '1' : '0');
+ elem.setAttribute('wrap', (wrap) ? '1' : '0');
+ elem.setAttribute('format', format);
+ root.appendChild(elem);
+ }
+ },
+
+ /**
+ * Function: begin
+ *
+ * Starts a new path.
+ */
+ begin: function()
+ {
+ root.appendChild(doc.createElement('begin'));
+ },
+
+ /**
+ * Function: moveTo
+ *
+ * Moves the current path the given coordinates.
+ */
+ moveTo: function(x, y)
+ {
+ var elem = doc.createElement('move');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: lineTo
+ *
+ * Adds a line to the current path.
+ */
+ lineTo: function(x, y)
+ {
+ var elem = doc.createElement('line');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: quadTo
+ *
+ * Adds a quadratic curve to the current path.
+ */
+ quadTo: function(x1, y1, x2, y2)
+ {
+ var elem = doc.createElement('quad');
+ elem.setAttribute('x1', f2(x1));
+ elem.setAttribute('y1', f2(y1));
+ elem.setAttribute('x2', f2(x2));
+ elem.setAttribute('y2', f2(y2));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: curveTo
+ *
+ * Adds a bezier curve to the current path.
+ */
+ curveTo: function(x1, y1, x2, y2, x3, y3)
+ {
+ var elem = doc.createElement('curve');
+ elem.setAttribute('x1', f2(x1));
+ elem.setAttribute('y1', f2(y1));
+ elem.setAttribute('x2', f2(x2));
+ elem.setAttribute('y2', f2(y2));
+ elem.setAttribute('x3', f2(x3));
+ elem.setAttribute('y3', f2(y3));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: close
+ *
+ * Closes the current path.
+ */
+ close: function()
+ {
+ root.appendChild(doc.createElement('close'));
+ },
+
+ /**
+ * Function: stroke
+ *
+ * Paints the outline of the current path.
+ */
+ stroke: function()
+ {
+ root.appendChild(doc.createElement('stroke'));
+ },
+
+ /**
+ * Function: fill
+ *
+ * Fills the current path.
+ */
+ fill: function()
+ {
+ root.appendChild(doc.createElement('fill'));
+ },
+
+ /**
+ * Function: fillstroke
+ *
+ * Fills and paints the outline of the current path.
+ */
+ fillAndStroke: function()
+ {
+ root.appendChild(doc.createElement('fillstroke'));
+ },
+
+ /**
+ * Function: shadow
+ *
+ * Paints the current path as a shadow of the given color.
+ */
+ shadow: function(value, filled)
+ {
+ var elem = doc.createElement('shadow');
+ elem.setAttribute('value', value);
+
+ if (filled != null)
+ {
+ elem.setAttribute('filled', (filled) ? '1' : '0');
+ }
+
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: clip
+ *
+ * Uses the current path for clipping.
+ */
+ clip: function()
+ {
+ root.appendChild(doc.createElement('clip'));
+ }
+ };
+
+}; \ No newline at end of file
diff --git a/src/js/util/mxXmlRequest.js b/src/js/util/mxXmlRequest.js
new file mode 100644
index 0000000..0ac55ed
--- /dev/null
+++ b/src/js/util/mxXmlRequest.js
@@ -0,0 +1,425 @@
+/**
+ * $Id: mxXmlRequest.js,v 1.38 2012-04-22 10:16:23 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxXmlRequest
+ *
+ * XML HTTP request wrapper. See also: <mxUtils.get>, <mxUtils.post> and
+ * <mxUtils.load>. This class provides a cross-browser abstraction for Ajax
+ * requests.
+ *
+ * Encoding:
+ *
+ * For encoding parameter values, the built-in encodeURIComponent JavaScript
+ * method must be used. For automatic encoding of post data in <mxEditor> the
+ * <mxEditor.escapePostData> switch can be set to true (default). The encoding
+ * will be carried out using the conte type of the page. That is, the page
+ * containting the editor should contain a meta tag in the header, eg.
+ * <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ *
+ * Example:
+ *
+ * (code)
+ * var onload = function(req)
+ * {
+ * mxUtils.alert(req.getDocumentElement());
+ * }
+ *
+ * var onerror = function(req)
+ * {
+ * mxUtils.alert(req.getStatus());
+ * }
+ * new mxXmlRequest(url, 'key=value').send(onload, onerror);
+ * (end)
+ *
+ * Sends an asynchronous POST request to the specified URL.
+ *
+ * Example:
+ *
+ * (code)
+ * var req = new mxXmlRequest(url, 'key=value', 'POST', false);
+ * req.send();
+ * mxUtils.alert(req.getDocumentElement());
+ * (end)
+ *
+ * Sends a synchronous POST request to the specified URL.
+ *
+ * Example:
+ *
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = encodeURIComponent(mxUtils.getXml(result));
+ * new mxXmlRequest(url, 'xml='+xml).send();
+ * (end)
+ *
+ * Sends an encoded graph model to the specified URL using xml as the
+ * parameter name. The parameter can then be retrieved in C# as follows:
+ *
+ * (code)
+ * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]);
+ * (end)
+ *
+ * Or in Java as follows:
+ *
+ * (code)
+ * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;");
+ * (end)
+ *
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image.
+ *
+ * Constructor: mxXmlRequest
+ *
+ * Constructs an XML HTTP request.
+ *
+ * Parameters:
+ *
+ * url - Target URL of the request.
+ * params - Form encoded parameters to send with a POST request.
+ * method - String that specifies the request method. Possible values are
+ * POST and GET. Default is POST.
+ * async - Boolean specifying if an asynchronous request should be used.
+ * Default is true.
+ * username - String specifying the username to be used for the request.
+ * password - String specifying the password to be used for the request.
+ */
+function mxXmlRequest(url, params, method, async, username, password)
+{
+ this.url = url;
+ this.params = params;
+ this.method = method || 'POST';
+ this.async = (async != null) ? async : true;
+ this.username = username;
+ this.password = password;
+};
+
+/**
+ * Variable: url
+ *
+ * Holds the target URL of the request.
+ */
+mxXmlRequest.prototype.url = null;
+
+/**
+ * Variable: params
+ *
+ * Holds the form encoded data for the POST request.
+ */
+mxXmlRequest.prototype.params = null;
+
+/**
+ * Variable: method
+ *
+ * Specifies the request method. Possible values are POST and GET. Default
+ * is POST.
+ */
+mxXmlRequest.prototype.method = null;
+
+/**
+ * Variable: async
+ *
+ * Boolean indicating if the request is asynchronous.
+ */
+mxXmlRequest.prototype.async = null;
+
+/**
+ * Variable: binary
+ *
+ * Boolean indicating if the request is binary. This option is ignored in IE.
+ * In all other browsers the requested mime type is set to
+ * text/plain; charset=x-user-defined. Default is false.
+ */
+mxXmlRequest.prototype.binary = false;
+
+/**
+ * Variable: 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;
+
+/**
+ * Function: isBinary
+ *
+ * Returns <binary>.
+ */
+mxXmlRequest.prototype.isBinary = function()
+{
+ return this.binary;
+};
+
+/**
+ * Function: setBinary
+ *
+ * Sets <binary>.
+ */
+mxXmlRequest.prototype.setBinary = function(value)
+{
+ this.binary = value;
+};
+
+/**
+ * Function: getText
+ *
+ * Returns the response as a string.
+ */
+mxXmlRequest.prototype.getText = function()
+{
+ return this.request.responseText;
+};
+
+/**
+ * Function: isReady
+ *
+ * Returns true if the response is ready.
+ */
+mxXmlRequest.prototype.isReady = function()
+{
+ return this.request.readyState == 4;
+};
+
+/**
+ * Function: getDocumentElement
+ *
+ * Returns the document element of the response XML document.
+ */
+mxXmlRequest.prototype.getDocumentElement = function()
+{
+ var doc = this.getXml();
+
+ if (doc != null)
+ {
+ return doc.documentElement;
+ }
+
+ return null;
+};
+
+/**
+ * Function: getXml
+ *
+ * Returns the response as an XML document. Use <getDocumentElement> to get
+ * the document element of the XML document.
+ */
+mxXmlRequest.prototype.getXml = function()
+{
+ var xml = this.request.responseXML;
+
+ // Handles missing response headers in IE, the first condition handles
+ // the case where responseXML is there, but using its nodes leads to
+ // type errors in the mxCellCodec when putting the nodes into a new
+ // document. This happens in IE9 standards mode and with XML user
+ // objects only, as they are used directly as values in cells.
+ if (document.documentMode >= 9 || xml == null || xml.documentElement == null)
+ {
+ xml = mxUtils.parseXml(this.request.responseText);
+ }
+
+ return xml;
+};
+
+/**
+ * Function: 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.status;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the inner <request> object.
+ */
+mxXmlRequest.prototype.create = function()
+{
+ if (window.XMLHttpRequest)
+ {
+ return function()
+ {
+ var req = new XMLHttpRequest();
+
+ // TODO: Check for overrideMimeType required here?
+ if (this.isBinary() && req.overrideMimeType)
+ {
+ req.overrideMimeType('text/plain; charset=x-user-defined');
+ }
+
+ return req;
+ };
+ }
+ else if (typeof(ActiveXObject) != "undefined")
+ {
+ return function()
+ {
+ // TODO: Implement binary option
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ };
+ }
+}();
+
+/**
+ * Function: send
+ *
+ * Send the <request> to the target URL using the specified functions to
+ * process the response asychronously.
+ *
+ * Parameters:
+ *
+ * onload - Function to be invoked if a successful response was received.
+ * onerror - Function to be called on any error.
+ */
+mxXmlRequest.prototype.send = function(onload, onerror)
+{
+ this.request = this.create();
+
+ if (this.request != null)
+ {
+ if (onload != null)
+ {
+ this.request.onreadystatechange = mxUtils.bind(this, function()
+ {
+ if (this.isReady())
+ {
+ onload(this);
+ this.onreadystatechaange = null;
+ }
+ });
+ }
+
+ this.request.open(this.method, this.url, this.async,
+ this.username, this.password);
+ this.setRequestHeaders(this.request, this.params);
+ this.request.send(this.params);
+ }
+};
+
+/**
+ * Function: setRequestHeaders
+ *
+ * Sets the headers for the given request and parameters. This sets the
+ * content-type to application/x-www-form-urlencoded if any params exist.
+ *
+ * Example:
+ *
+ * (code)
+ * request.setRequestHeaders = function(request, params)
+ * {
+ * if (params != null)
+ * {
+ * request.setRequestHeader('Content-Type',
+ * 'multipart/form-data');
+ * request.setRequestHeader('Content-Length',
+ * params.length);
+ * }
+ * };
+ * (end)
+ *
+ * Use the code above before calling <send> if you require a
+ * multipart/form-data request.
+ */
+mxXmlRequest.prototype.setRequestHeaders = function(request, params)
+{
+ if (params != null)
+ {
+ request.setRequestHeader('Content-Type',
+ 'application/x-www-form-urlencoded');
+ }
+};
+
+/**
+ * Function: simulate
+ *
+ * Creates and posts a request to the given target URL using a dynamically
+ * created form inside the given document.
+ *
+ * Parameters:
+ *
+ * docs - Document that contains the form element.
+ * target - Target to send the form result to.
+ */
+mxXmlRequest.prototype.simulate = function(doc, target)
+{
+ doc = doc || document;
+ var old = null;
+
+ if (doc == document)
+ {
+ old = window.onbeforeunload;
+ window.onbeforeunload = null;
+ }
+
+ var form = doc.createElement('form');
+ form.setAttribute('method', this.method);
+ form.setAttribute('action', this.url);
+
+ if (target != null)
+ {
+ form.setAttribute('target', target);
+ }
+
+ form.style.display = 'none';
+ form.style.visibility = 'hidden';
+
+ var pars = (this.params.indexOf('&') > 0) ?
+ this.params.split('&') :
+ this.params.split();
+
+ // Adds the parameters as textareas to the form
+ for (var i=0; i<pars.length; i++)
+ {
+ var pos = pars[i].indexOf('=');
+
+ if (pos > 0)
+ {
+ var name = pars[i].substring(0, pos);
+ var value = pars[i].substring(pos+1);
+
+ var textarea = doc.createElement('textarea');
+ textarea.setAttribute('name', name);
+ value = value.replace(/\n/g, '&#xa;');
+
+ var content = doc.createTextNode(value);
+ textarea.appendChild(content);
+ form.appendChild(textarea);
+ }
+ }
+
+ doc.body.appendChild(form);
+ form.submit();
+ doc.body.removeChild(form);
+
+ if (old != null)
+ {
+ window.onbeforeunload = old;
+ }
+};
diff --git a/src/js/view/mxCellEditor.js b/src/js/view/mxCellEditor.js
new file mode 100644
index 0000000..2086cca
--- /dev/null
+++ b/src/js/view/mxCellEditor.js
@@ -0,0 +1,522 @@
+/**
+ * $Id: mxCellEditor.js,v 1.62 2012-12-11 16:59:31 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellEditor
+ *
+ * In-place editor for the graph. To control this editor, use
+ * <mxGraph.invokesStopCellEditing>, <mxGraph.enterStopsCellEditing> and
+ * <mxGraph.escapeEnabled>. If <mxGraph.enterStopsCellEditing> is true then
+ * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and
+ * escape keys can always be used to stop editing. To customize the location
+ * of the textbox in the graph, override <getEditorBounds> as follows:
+ *
+ * (code)
+ * graph.cellEditor.getEditorBounds = function(state)
+ * {
+ * var result = mxCellEditor.prototype.getEditorBounds.apply(this, arguments);
+ *
+ * if (this.graph.getModel().isEdge(state.cell))
+ * {
+ * result.x = state.getCenterX() - result.width / 2;
+ * result.y = state.getCenterY() - result.height / 2;
+ * }
+ *
+ * return result;
+ * };
+ * (end)
+ *
+ * The textarea uses the mxCellEditor CSS class. You can modify this class in
+ * your custom CSS. Note: You should modify the CSS after loading the client
+ * in the page.
+ *
+ * Example:
+ *
+ * To only allow numeric input in the in-place editor, use the following code.
+ *
+ * (code)
+ * var text = graph.cellEditor.textarea;
+ *
+ * mxEvent.addListener(text, 'keydown', function (evt)
+ * {
+ * if (!(evt.keyCode >= 48 && evt.keyCode <= 57) &&
+ * !(evt.keyCode >= 96 && evt.keyCode <= 105))
+ * {
+ * mxEvent.consume(evt);
+ * }
+ * });
+ * (end)
+ *
+ * Initial values:
+ *
+ * To implement an initial value for cells without a label, use the
+ * <emptyLabelText> variable.
+ *
+ * Resize in Chrome:
+ *
+ * Resize of the textarea is disabled by default. If you want to enable
+ * this feature extend <init> and set this.textarea.style.resize = ''.
+ *
+ * Constructor: mxCellEditor
+ *
+ * Constructs a new in-place editor for the specified graph.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellEditor(graph)
+{
+ this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellEditor.prototype.graph = null;
+
+/**
+ * Variable: textarea
+ *
+ * Holds the input textarea. Note that this may be null before the first
+ * edit. Instantiated in <init>.
+ */
+mxCellEditor.prototype.textarea = null;
+
+/**
+ * Variable: editingCell
+ *
+ * Reference to the <mxCell> that is currently being edited.
+ */
+mxCellEditor.prototype.editingCell = null;
+
+/**
+ * Variable: trigger
+ *
+ * Reference to the event that was used to start editing.
+ */
+mxCellEditor.prototype.trigger = null;
+
+/**
+ * Variable: modified
+ *
+ * Specifies if the label has been modified.
+ */
+mxCellEditor.prototype.modified = false;
+
+/**
+ * Variable: emptyLabelText
+ *
+ * Text to be displayed for empty labels. Default is ''. This can be set
+ * to eg. "[Type Here]" to easier visualize editing of empty labels. The
+ * value is only displayed before the first keystroke and is never used
+ * as the actual editin value.
+ */
+mxCellEditor.prototype.emptyLabelText = '';
+
+/**
+ * Variable: textNode
+ *
+ * Reference to the label DOM node that has been hidden.
+ */
+mxCellEditor.prototype.textNode = '';
+
+/**
+ * Function: init
+ *
+ * Creates the <textarea> and installs the event listeners. The key handler
+ * updates the <modified> state.
+ */
+mxCellEditor.prototype.init = function ()
+{
+ this.textarea = document.createElement('textarea');
+
+ this.textarea.className = 'mxCellEditor';
+ this.textarea.style.position = 'absolute';
+ this.textarea.style.overflow = 'visible';
+
+ this.textarea.setAttribute('cols', '20');
+ this.textarea.setAttribute('rows', '4');
+
+ if (mxClient.IS_GC)
+ {
+ this.textarea.style.resize = 'none';
+ }
+
+ mxEvent.addListener(this.textarea, 'blur', mxUtils.bind(this, function(evt)
+ {
+ this.focusLost();
+ }));
+
+ mxEvent.addListener(this.textarea, 'keydown', mxUtils.bind(this, function(evt)
+ {
+ if (!mxEvent.isConsumed(evt))
+ {
+ if (evt.keyCode == 113 /* F2 */ || (this.graph.isEnterStopsCellEditing() &&
+ evt.keyCode == 13 /* Enter */ && !mxEvent.isControlDown(evt) &&
+ !mxEvent.isShiftDown(evt)))
+ {
+ this.graph.stopEditing(false);
+ mxEvent.consume(evt);
+ }
+ else if (evt.keyCode == 27 /* Escape */)
+ {
+ this.graph.stopEditing(true);
+ mxEvent.consume(evt);
+ }
+ else
+ {
+ // Clears the initial empty label on the first keystroke
+ if (this.clearOnChange)
+ {
+ this.clearOnChange = false;
+ this.textarea.value = '';
+ }
+
+ // Updates the modified flag for storing the value
+ this.setModified(true);
+ }
+ }
+ }));
+};
+
+/**
+ * Function: isModified
+ *
+ * Returns <modified>.
+ */
+mxCellEditor.prototype.isModified = function()
+{
+ return this.modified;
+};
+
+/**
+ * Function: setModified
+ *
+ * Sets <modified> to the specified boolean value.
+ */
+mxCellEditor.prototype.setModified = function(value)
+{
+ this.modified = value;
+};
+
+/**
+ * Function: focusLost
+ *
+ * Called if the textarea has lost focus.
+ */
+mxCellEditor.prototype.focusLost = function()
+{
+ this.stopEditing(!this.graph.isInvokesStopCellEditing());
+};
+
+/**
+ * Function: startEditing
+ *
+ * Starts the editor for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to start editing.
+ * trigger - Optional mouse event that triggered the editor.
+ */
+mxCellEditor.prototype.startEditing = function(cell, trigger)
+{
+ // Lazy instantiates textarea to save memory in IE
+ if (this.textarea == null)
+ {
+ this.init();
+ }
+
+ this.stopEditing(true);
+ var state = this.graph.getView().getState(cell);
+
+ if (state != null)
+ {
+ this.editingCell = cell;
+ this.trigger = trigger;
+ this.textNode = null;
+
+ if (state.text != null && this.isHideLabel(state))
+ {
+ this.textNode = state.text.node;
+ this.textNode.style.visibility = 'hidden';
+ }
+
+ // Configures the style of the in-place editor
+ var scale = this.graph.getView().scale;
+ var size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) * scale;
+ var family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY);
+ var color = mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, 'black');
+ var align = (this.graph.model.isEdge(state.cell)) ? mxConstants.ALIGN_LEFT :
+ mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
+ var bold = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+ mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD;
+ var italic = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+ mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC;
+ var uline = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+ mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE;
+
+ this.textarea.style.fontSize = size + 'px';
+ this.textarea.style.fontFamily = family;
+ this.textarea.style.textAlign = align;
+ this.textarea.style.color = color;
+ this.textarea.style.fontWeight = (bold) ? 'bold' : 'normal';
+ this.textarea.style.fontStyle = (italic) ? 'italic' : '';
+ this.textarea.style.textDecoration = (uline) ? 'underline' : '';
+
+ // Specifies the bounds of the editor box
+ var bounds = this.getEditorBounds(state);
+
+ this.textarea.style.left = bounds.x + 'px';
+ this.textarea.style.top = bounds.y + 'px';
+ this.textarea.style.width = bounds.width + 'px';
+ this.textarea.style.height = bounds.height + 'px';
+ this.textarea.style.zIndex = 5;
+
+ var value = this.getInitialValue(state, trigger);
+
+ // Uses an optional text value for empty labels which is cleared
+ // when the first keystroke appears. This makes it easier to see
+ // that a label is being edited even if the label is empty.
+ if (value == null || value.length == 0)
+ {
+ value = this.getEmptyLabelText();
+ this.clearOnChange = true;
+ }
+ else
+ {
+ this.clearOnChange = false;
+ }
+
+ this.setModified(false);
+ this.textarea.value = value;
+ this.graph.container.appendChild(this.textarea);
+
+ if (this.textarea.style.display != 'none')
+ {
+ // FIXME: Doesn't bring up the virtual keyboard on iPad
+ this.textarea.focus();
+ this.textarea.select();
+ }
+ }
+};
+
+/**
+ * Function: stopEditing
+ *
+ * Stops the editor and applies the value if cancel is false.
+ */
+mxCellEditor.prototype.stopEditing = function(cancel)
+{
+ cancel = cancel || false;
+
+ if (this.editingCell != null)
+ {
+ if (this.textNode != null)
+ {
+ this.textNode.style.visibility = 'visible';
+ this.textNode = null;
+ }
+
+ if (!cancel && this.isModified())
+ {
+ this.graph.labelChanged(this.editingCell, this.getCurrentValue(), this.trigger);
+ }
+
+ this.editingCell = null;
+ this.trigger = null;
+ this.textarea.blur();
+ this.textarea.parentNode.removeChild(this.textarea);
+ }
+};
+
+/**
+ * Function: getInitialValue
+ *
+ * Gets the initial editing value for the given cell.
+ */
+mxCellEditor.prototype.getInitialValue = function(state, trigger)
+{
+ return this.graph.getEditingValue(state.cell, trigger);
+};
+
+/**
+ * Function: getCurrentValue
+ *
+ * Returns the current editing value.
+ */
+mxCellEditor.prototype.getCurrentValue = function()
+{
+ return this.textarea.value.replace(/\r/g, '');
+};
+
+/**
+ * Function: isHideLabel
+ *
+ * Returns true if the label should be hidden while the cell is being
+ * edited.
+ */
+mxCellEditor.prototype.isHideLabel = function(state)
+{
+ return true;
+};
+
+/**
+ * Function: getMinimumSize
+ *
+ * Returns the minimum width and height for editing the given state.
+ */
+mxCellEditor.prototype.getMinimumSize = function(state)
+{
+ var scale = this.graph.getView().scale;
+
+ return new mxRectangle(0, 0, (state.text == null) ? 30 : state.text.size * scale + 20,
+ (this.textarea.style.textAlign == 'left') ? 120 : 40);
+};
+
+/**
+ * Function: getEditorBounds
+ *
+ * Returns the <mxRectangle> that defines the bounds of the editor.
+ */
+mxCellEditor.prototype.getEditorBounds = function(state)
+{
+ var isEdge = this.graph.getModel().isEdge(state.cell);
+ var scale = this.graph.getView().scale;
+ var minSize = this.getMinimumSize(state);
+ var minWidth = minSize.width;
+ var minHeight = minSize.height;
+
+ var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 2) * scale;
+ var spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0)) * scale + spacing;
+ var spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0)) * scale + spacing;
+ var spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0)) * scale + spacing;
+ var spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0)) * scale + spacing;
+
+ var result = new mxRectangle(state.x, state.y,
+ Math.max(minWidth, state.width - spacingLeft - spacingRight),
+ Math.max(minHeight, state.height - spacingTop - spacingBottom));
+
+ if (isEdge)
+ {
+ result.x = state.absoluteOffset.x;
+ result.y = state.absoluteOffset.y;
+
+ if (state.text != null && state.text.boundingBox != null)
+ {
+ // Workaround for label containing just spaces in which case
+ // the bounding box location contains negative numbers
+ if (state.text.boundingBox.x > 0)
+ {
+ result.x = state.text.boundingBox.x;
+ }
+
+ if (state.text.boundingBox.y > 0)
+ {
+ result.y = state.text.boundingBox.y;
+ }
+ }
+ }
+ else if (state.text != null && state.text.boundingBox != null)
+ {
+ result.x = Math.min(result.x, state.text.boundingBox.x);
+ result.y = Math.min(result.y, state.text.boundingBox.y);
+ }
+
+ result.x += spacingLeft;
+ result.y += spacingTop;
+
+ if (state.text != null && state.text.boundingBox != null)
+ {
+ if (!isEdge)
+ {
+ result.width = Math.max(result.width, state.text.boundingBox.width);
+ result.height = Math.max(result.height, state.text.boundingBox.height);
+ }
+ else
+ {
+ result.width = Math.max(minWidth, state.text.boundingBox.width);
+ result.height = Math.max(minHeight, state.text.boundingBox.height);
+ }
+ }
+
+ // Applies the horizontal and vertical label positions
+ if (this.graph.getModel().isVertex(state.cell))
+ {
+ var horizontal = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+
+ if (horizontal == mxConstants.ALIGN_LEFT)
+ {
+ result.x -= state.width;
+ }
+ else if (horizontal == mxConstants.ALIGN_RIGHT)
+ {
+ result.x += state.width;
+ }
+
+ var vertical = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+
+ if (vertical == mxConstants.ALIGN_TOP)
+ {
+ result.y -= state.height;
+ }
+ else if (vertical == mxConstants.ALIGN_BOTTOM)
+ {
+ result.y += state.height;
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getEmptyLabelText
+ *
+ * Returns the initial label value to be used of the label of the given
+ * cell is empty. This label is displayed and cleared on the first keystroke.
+ * This implementation returns <emptyLabelText>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which a text for an empty editing box should be
+ * returned.
+ */
+mxCellEditor.prototype.getEmptyLabelText = function (cell)
+{
+ return this.emptyLabelText;
+};
+
+/**
+ * Function: getEditingCell
+ *
+ * Returns the cell that is currently being edited or null if no cell is
+ * being edited.
+ */
+mxCellEditor.prototype.getEditingCell = function ()
+{
+ return this.editingCell;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the editor and removes all associated resources.
+ */
+mxCellEditor.prototype.destroy = function ()
+{
+ if (this.textarea != null)
+ {
+ mxEvent.release(this.textarea);
+
+ if (this.textarea.parentNode != null)
+ {
+ this.textarea.parentNode.removeChild(this.textarea);
+ }
+
+ this.textarea = null;
+ }
+};
diff --git a/src/js/view/mxCellOverlay.js b/src/js/view/mxCellOverlay.js
new file mode 100644
index 0000000..316e2c4
--- /dev/null
+++ b/src/js/view/mxCellOverlay.js
@@ -0,0 +1,233 @@
+/**
+ * $Id: mxCellOverlay.js,v 1.18 2012-12-06 15:58:44 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellOverlay
+ *
+ * Extends <mxEventSource> to implement a graph overlay, represented by an icon
+ * and a tooltip. Overlays can handle and fire <click> events and are added to
+ * the graph using <mxGraph.addCellOverlay>, and removed using
+ * <mxGraph.removeCellOverlay>, or <mxGraph.removeCellOverlays> to remove all overlays.
+ * The <mxGraph.getCellOverlays> function returns the array of overlays for a given
+ * cell in a graph. If multiple overlays exist for the same cell, then
+ * <getBounds> should be overridden in at least one of the overlays.
+ *
+ * Overlays appear on top of all cells in a special layer. If this is not
+ * desirable, then the image must be rendered as part of the shape or label of
+ * the cell instead.
+ *
+ * Example:
+ *
+ * The following adds a new overlays for a given vertex and selects the cell
+ * if the overlay is clicked.
+ *
+ * (code)
+ * var overlay = new mxCellOverlay(img, html);
+ * graph.addCellOverlay(vertex, overlay);
+ * overlay.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ * var cell = evt.getProperty('cell');
+ * graph.setSelectionCell(cell);
+ * });
+ * (end)
+ *
+ * For cell overlays to be printed use <mxPrintPreview.printOverlays>.
+ *
+ * Event: mxEvent.CLICK
+ *
+ * Fires when the user clicks on the overlay. The <code>event</code> property
+ * contains the corresponding mouse event and the <code>cell</code> property
+ * contains the cell. For touch devices this is fired if the element receives
+ * a touchend event.
+ *
+ * Constructor: mxCellOverlay
+ *
+ * Constructs a new overlay using the given image and tooltip.
+ *
+ * Parameters:
+ *
+ * image - <mxImage> that represents the icon to be displayed.
+ * tooltip - Optional string that specifies the tooltip.
+ * align - Optional horizontal alignment for the overlay. Possible
+ * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>
+ * (default).
+ * verticalAlign - Vertical alignment for the overlay. Possible
+ * values are <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>
+ * (default).
+ */
+function mxCellOverlay(image, tooltip, align, verticalAlign, offset, cursor)
+{
+ this.image = image;
+ this.tooltip = tooltip;
+ this.align = (align != null) ? align : this.align;
+ this.verticalAlign = (verticalAlign != null) ? verticalAlign : this.verticalAlign;
+ this.offset = (offset != null) ? offset : new mxPoint();
+ this.cursor = (cursor != null) ? cursor : 'help';
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxCellOverlay.prototype = new mxEventSource();
+mxCellOverlay.prototype.constructor = mxCellOverlay;
+
+/**
+ * Variable: image
+ *
+ * Holds the <mxImage> to be used as the icon.
+ */
+mxCellOverlay.prototype.image = null;
+
+/**
+ * Variable: tooltip
+ *
+ * Holds the optional string to be used as the tooltip.
+ */
+mxCellOverlay.prototype.tooltip = null;
+
+/**
+ * Variable: align
+ *
+ * Holds the horizontal alignment for the overlay. Default is
+ * <mxConstants.ALIGN_RIGHT>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.align = mxConstants.ALIGN_RIGHT;
+
+/**
+ * Variable: verticalAlign
+ *
+ * Holds the vertical alignment for the overlay. Default is
+ * <mxConstants.ALIGN_BOTTOM>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.verticalAlign = mxConstants.ALIGN_BOTTOM;
+
+/**
+ * Variable: offset
+ *
+ * Holds the offset as an <mxPoint>. The offset will be scaled according to the
+ * current scale.
+ */
+mxCellOverlay.prototype.offset = null;
+
+/**
+ * Variable: cursor
+ *
+ * Holds the cursor for the overlay. Default is 'help'.
+ */
+mxCellOverlay.prototype.cursor = null;
+
+/**
+ * Variable: defaultOverlap
+ *
+ * Defines the overlapping for the overlay, that is, the proportional distance
+ * from the origin to the point defined by the alignment. Default is 0.5.
+ */
+mxCellOverlay.prototype.defaultOverlap = 0.5;
+
+/**
+ * Function: getBounds
+ *
+ * Returns the bounds of the overlay for the given <mxCellState> as an
+ * <mxRectangle>. This should be overridden when using multiple overlays
+ * per cell so that the overlays do not overlap.
+ *
+ * The following example will place the overlay along an edge (where
+ * x=[-1..1] from the start to the end of the edge and y is the
+ * orthogonal offset in px).
+ *
+ * (code)
+ * overlay.getBounds = function(state)
+ * {
+ * var bounds = mxCellOverlay.prototype.getBounds.apply(this, arguments);
+ *
+ * if (state.view.graph.getModel().isEdge(state.cell))
+ * {
+ * var pt = state.view.getPoint(state, {x: 0, y: 0, relative: true});
+ *
+ * bounds.x = pt.x - bounds.width / 2;
+ * bounds.y = pt.y - bounds.height / 2;
+ * }
+ *
+ * return bounds;
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the current state of the
+ * associated cell.
+ */
+mxCellOverlay.prototype.getBounds = function(state)
+{
+ var isEdge = state.view.graph.getModel().isEdge(state.cell);
+ var s = state.view.scale;
+ var pt = null;
+
+ var w = this.image.width;
+ var h = this.image.height;
+
+ if (isEdge)
+ {
+ var pts = state.absolutePoints;
+
+ if (pts.length % 2 == 1)
+ {
+ pt = pts[Math.floor(pts.length / 2)];
+ }
+ else
+ {
+ var idx = pts.length / 2;
+ var p0 = pts[idx-1];
+ var p1 = pts[idx];
+ pt = new mxPoint(p0.x + (p1.x - p0.x) / 2,
+ p0.y + (p1.y - p0.y) / 2);
+ }
+ }
+ else
+ {
+ pt = new mxPoint();
+
+ if (this.align == mxConstants.ALIGN_LEFT)
+ {
+ pt.x = state.x;
+ }
+ else if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ pt.x = state.x + state.width / 2;
+ }
+ else
+ {
+ pt.x = state.x + state.width;
+ }
+
+ if (this.verticalAlign == mxConstants.ALIGN_TOP)
+ {
+ pt.y = state.y;
+ }
+ else if (this.verticalAlign == mxConstants.ALIGN_MIDDLE)
+ {
+ pt.y = state.y + state.height / 2;
+ }
+ else
+ {
+ pt.y = state.y + state.height;
+ }
+ }
+
+ return new mxRectangle(pt.x - (w * this.defaultOverlap - this.offset.x) * s,
+ pt.y - (h * this.defaultOverlap - this.offset.y) * s, w * s, h * s);
+};
+
+/**
+ * Function: toString
+ *
+ * Returns the textual representation of the overlay to be used as the
+ * tooltip. This implementation returns <tooltip>.
+ */
+mxCellOverlay.prototype.toString = function()
+{
+ return this.tooltip;
+};
diff --git a/src/js/view/mxCellRenderer.js b/src/js/view/mxCellRenderer.js
new file mode 100644
index 0000000..6b506ad
--- /dev/null
+++ b/src/js/view/mxCellRenderer.js
@@ -0,0 +1,1480 @@
+/**
+ * $Id: mxCellRenderer.js,v 1.189 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellRenderer
+ *
+ * Renders cells into a document object model. The <defaultShapes> is a global
+ * map of shapename, constructor pairs that is used in all instances. You can
+ * get a list of all available shape names using the following code.
+ *
+ * In general the cell renderer is in charge of creating, redrawing and
+ * destroying the shape and label associated with a cell state, as well as
+ * some other graphical objects, namely controls and overlays. The shape
+ * hieararchy in the display (ie. the hierarchy in which the DOM nodes
+ * appear in the document) does not reflect the cell hierarchy. The shapes
+ * are a (flat) sequence of shapes and labels inside the draw pane of the
+ * graph view, with some exceptions, namely the HTML labels being placed
+ * directly inside the graph container for certain browsers.
+ *
+ * (code)
+ * mxLog.show();
+ * for (var i in mxCellRenderer.prototype.defaultShapes)
+ * {
+ * mxLog.debug(i);
+ * }
+ * (end)
+ *
+ * Constructor: mxCellRenderer
+ *
+ * Constructs a new cell renderer with the following built-in shapes:
+ * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,
+ * swimlane, connector, actor and cloud.
+ */
+function mxCellRenderer()
+{
+ this.shapes = mxUtils.clone(this.defaultShapes);
+};
+
+/**
+ * Variable: shapes
+ *
+ * Array that maps from shape names to shape constructors. All entries
+ * in <defaultShapes> are added to this array.
+ */
+mxCellRenderer.prototype.shapes = null;
+
+/**
+ * Variable: defaultEdgeShape
+ *
+ * Defines the default shape for edges. Default is <mxConnector>.
+ */
+mxCellRenderer.prototype.defaultEdgeShape = mxConnector;
+
+/**
+ * Variable: defaultVertexShape
+ *
+ * Defines the default shape for vertices. Default is <mxRectangleShape>.
+ */
+mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;
+
+/**
+ * Variable: defaultShapes
+ *
+ * Static array that contains the globally registered shapes which are
+ * known to all instances of this class. For adding instance-specific
+ * shapes you should use <registerShape> on the instance. For adding
+ * a shape to this array you can use the following code:
+ *
+ * (code)
+ * mxCellRenderer.prototype.defaultShapes['myshape'] = myShape;
+ * (end)
+ *
+ * Where 'myshape' is the key under which the shape is to be registered
+ * and myShape is the name of the constructor function.
+ */
+mxCellRenderer.prototype.defaultShapes = new Object();
+
+// Adds default shapes into the default shapes array
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ARROW] = mxArrow;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_RECTANGLE] = mxRectangleShape;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ELLIPSE] = mxEllipse;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_DOUBLE_ELLIPSE] = mxDoubleEllipse;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_RHOMBUS] = mxRhombus;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_IMAGE] = mxImageShape;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_LINE] = mxLine;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_LABEL] = mxLabel;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CYLINDER] = mxCylinder;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_SWIMLANE] = mxSwimlane;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CONNECTOR] = mxConnector;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ACTOR] = mxActor;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CLOUD] = mxCloud;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_TRIANGLE] = mxTriangle;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_HEXAGON] = mxHexagon;
+
+/**
+ * Function: registerShape
+ *
+ * Registers the given constructor under the specified key in this instance
+ * of the renderer.
+ *
+ * Example:
+ *
+ * (code)
+ * this.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+ * (end)
+ *
+ * Parameters:
+ *
+ * key - String representing the shape name.
+ * shape - Constructor of the <mxShape> subclass.
+ */
+mxCellRenderer.prototype.registerShape = function(key, shape)
+{
+ this.shapes[key] = shape;
+};
+
+/**
+ * Function: initialize
+ *
+ * Initializes the display for the given cell state. This is required once
+ * after the cell state has been created. This is invoked in
+ * mxGraphView.createState.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the display should be initialized.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be initialized for any given DOM node. If this is false then init
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.initialize = function(state, rendering)
+{
+ var model = state.view.graph.getModel();
+
+ if (state.view.graph.container != null && state.shape == null &&
+ state.cell != state.view.currentRoot &&
+ (model.isVertex(state.cell) || model.isEdge(state.cell)))
+ {
+ this.createShape(state);
+
+ if (state.shape != null && (rendering == null || rendering))
+ {
+ this.initializeShape(state);
+
+ // Maintains the model order in the DOM
+ if (state.view.graph.ordered || model.isEdge(state.cell))
+ {
+ //state.orderChanged = true;
+ state.invalidOrder = true;
+ }
+ else if (state.view.graph.keepEdgesInForeground && this.firstEdge != null)
+ {
+ if (this.firstEdge.parentNode == state.shape.node.parentNode)
+ {
+ this.insertState(state, this.firstEdge);
+ }
+ else
+ {
+ this.firstEdge = null;
+ }
+ }
+
+ state.shape.scale = state.view.scale;
+
+ this.createCellOverlays(state);
+ this.installListeners(state);
+ }
+ }
+};
+
+/**
+ * Function: initializeShape
+ *
+ * Initializes the shape in the given state by calling its init method with
+ * the correct container.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be initialized.
+ */
+mxCellRenderer.prototype.initializeShape = function(state)
+{
+ state.shape.init(state.view.getDrawPane());
+};
+
+/**
+ * Returns the previous state that has a shape inside the given parent.
+ */
+mxCellRenderer.prototype.getPreviousStateInContainer = function(state, container)
+{
+ var result = null;
+ var graph = state.view.graph;
+ var model = graph.getModel();
+ var child = state.cell;
+ var p = model.getParent(child);
+
+ while (p != null && result == null)
+ {
+ result = this.findPreviousStateInContainer(graph, p, child, container);
+ child = p;
+ p = model.getParent(child);
+ }
+
+ return result;
+};
+
+/**
+ * Returns the previous state that has a shape inside the given parent.
+ */
+mxCellRenderer.prototype.findPreviousStateInContainer = function(graph, cell, stop, container)
+{
+ // Recurse first
+ var result = null;
+ var model = graph.getModel();
+
+ if (stop != null)
+ {
+ var start = cell.getIndex(stop);
+
+ for (var i = start - 1; i >= 0 && result == null; i--)
+ {
+ result = this.findPreviousStateInContainer(graph, model.getChildAt(cell, i), null, container);
+ }
+ }
+ else
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = childCount - 1; i >= 0 && result == null; i--)
+ {
+ result = this.findPreviousStateInContainer(graph, model.getChildAt(cell, i), null, container);
+ }
+ }
+
+ if (result == null)
+ {
+ result = graph.view.getState(cell);
+
+ if (result != null && (result.shape == null || result.shape.node == null ||
+ result.shape.node.parentNode != container))
+ {
+ result = null;
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: order
+ *
+ * Orders the DOM node of the shape for the given state according to the
+ * position of the corresponding cell in the graph model.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape's DOM node should be ordered.
+ */
+mxCellRenderer.prototype.order = function(state)
+{
+ var container = state.shape.node.parentNode;
+ var previous = this.getPreviousStateInContainer(state, container);
+ var nextNode = container.firstChild;
+
+ if (previous != null)
+ {
+ nextNode = previous.shape.node;
+
+ if (previous.text != null && previous.text.node != null &&
+ previous.text.node.parentNode == container)
+ {
+ nextNode = previous.text.node;
+ }
+
+ nextNode = nextNode.nextSibling;
+ }
+
+ this.insertState(state, nextNode);
+};
+
+/**
+ * Function: orderEdge
+ *
+ * Orders the DOM node of the shape for the given edge's state according to
+ * the <mxGraph.keepEdgesInBackground> and <mxGraph.keepEdgesInBackground>
+ * rules.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape's DOM node should be ordered.
+ */
+mxCellRenderer.prototype.orderEdge = function(state)
+{
+ var view = state.view;
+ var model = view.graph.getModel();
+
+ // Moves edges to the foreground/background
+ if (view.graph.keepEdgesInForeground)
+ {
+ if (this.firstEdge == null || this.firstEdge.parentNode == null ||
+ this.firstEdge.parentNode != state.shape.node.parentNode)
+ {
+ this.firstEdge = state.shape.node;
+ }
+ }
+ else if (view.graph.keepEdgesInBackground)
+ {
+ var node = state.shape.node;
+ var parent = node.parentNode;
+
+ // Keeps the DOM node in front of its parent
+ var pcell = model.getParent(state.cell);
+ var pstate = view.getState(pcell);
+
+ if (pstate != null && pstate.shape != null && pstate.shape.node != null)
+ {
+ var child = pstate.shape.node.nextSibling;
+
+ if (child != null && child != node)
+ {
+ this.insertState(state, child);
+ }
+ }
+ else
+ {
+ var child = parent.firstChild;
+
+ if (child != null && child != node)
+ {
+ this.insertState(state, child);
+ }
+ }
+ }
+};
+
+/**
+ * Function: insertState
+ *
+ * Inserts the given state before the given node into its parent.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.insertState = function(state, nextNode)
+{
+ state.shape.node.parentNode.insertBefore(state.shape.node, nextNode);
+
+ if (state.text != null && state.text.node != null &&
+ state.text.node.parentNode == state.shape.node.parentNode)
+ {
+ state.shape.node.parentNode.insertBefore(state.text.node, state.shape.node.nextSibling);
+ }
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates the shape for the given cell state. The shape is configured
+ * using <configureShape>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.createShape = function(state)
+{
+ if (state.style != null)
+ {
+ // Checks if there is a stencil for the name and creates
+ // a shape instance for the stencil if one exists
+ var key = state.style[mxConstants.STYLE_SHAPE];
+ var stencil = mxStencilRegistry.getStencil(key);
+
+ if (stencil != null)
+ {
+ state.shape = new mxStencilShape(stencil);
+ }
+ else
+ {
+ var ctor = this.getShapeConstructor(state);
+ state.shape = new ctor();
+ }
+
+ // Sets the initial bounds and points (will be updated in redraw)
+ state.shape.points = state.absolutePoints;
+ state.shape.bounds = new mxRectangle(
+ state.x, state.y, state.width, state.height);
+ state.shape.dialect = state.view.graph.dialect;
+
+ this.configureShape(state);
+ }
+};
+
+/**
+ * Function: getShapeConstructor
+ *
+ * Returns the constructor to be used for creating the shape.
+ */
+mxCellRenderer.prototype.getShapeConstructor = function(state)
+{
+ var key = state.style[mxConstants.STYLE_SHAPE];
+ var ctor = (key != null) ? this.shapes[key] : null;
+
+ if (ctor == null)
+ {
+ ctor = (state.view.graph.getModel().isEdge(state.cell)) ?
+ this.defaultEdgeShape : this.defaultVertexShape;
+ }
+
+ return ctor;
+};
+
+/**
+ * Function: configureShape
+ *
+ * Configures the shape for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be configured.
+ */
+mxCellRenderer.prototype.configureShape = function(state)
+{
+ state.shape.apply(state);
+ var image = state.view.graph.getImage(state);
+
+ if (image != null)
+ {
+ state.shape.image = image;
+ }
+
+ var indicator = state.view.graph.getIndicatorColor(state);
+ var key = state.view.graph.getIndicatorShape(state);
+ var ctor = (key != null) ? this.shapes[key] : null;
+
+ // Configures the indicator shape or image
+ if (indicator != null)
+ {
+ state.shape.indicatorShape = ctor;
+ state.shape.indicatorColor = indicator;
+ state.shape.indicatorGradientColor =
+ state.view.graph.getIndicatorGradientColor(state);
+ state.shape.indicatorDirection =
+ state.style[mxConstants.STYLE_INDICATOR_DIRECTION];
+ }
+ else
+ {
+ var indicator = state.view.graph.getIndicatorImage(state);
+
+ if (indicator != null)
+ {
+ state.shape.indicatorImage = indicator;
+ }
+ }
+
+ this.postConfigureShape(state);
+};
+
+/**
+ * Function: postConfigureShape
+ *
+ * Replaces any reserved words used for attributes, eg. inherit,
+ * indicated or swimlane for colors in the shape for the given state.
+ * This implementation resolves these keywords on the fill, stroke
+ * and gradient color keys.
+ */
+mxCellRenderer.prototype.postConfigureShape = function(state)
+{
+ if (state.shape != null)
+ {
+ this.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);
+ this.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);
+ this.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);
+ this.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);
+ this.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);
+ }
+};
+
+/**
+ * Function: resolveColor
+ *
+ * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
+ * the respective color on the shape.
+ */
+mxCellRenderer.prototype.resolveColor = function(state, field, key)
+{
+ var value = state.shape[field];
+ var graph = state.view.graph;
+ var referenced = null;
+
+ if (value == 'inherit')
+ {
+ referenced = graph.model.getParent(state.cell);
+ }
+ else if (value == 'swimlane')
+ {
+ if (graph.model.getTerminal(state.cell, false) != null)
+ {
+ referenced = graph.model.getTerminal(state.cell, false);
+ }
+ else
+ {
+ referenced = state.cell;
+ }
+
+ referenced = graph.getSwimlane(referenced);
+ key = graph.swimlaneIndicatorColorAttribute;
+ }
+ else if (value == 'indicated')
+ {
+ state.shape[field] = state.shape.indicatorColor;
+ }
+
+ if (referenced != null)
+ {
+ var rstate = graph.getView().getState(referenced);
+ state.shape[field] = null;
+
+ if (rstate != null)
+ {
+ if (rstate.shape != null && field != 'indicatorColor')
+ {
+ state.shape[field] = rstate.shape[field];
+ }
+ else
+ {
+ state.shape[field] = rstate.style[key];
+ }
+ }
+ }
+};
+
+/**
+ * Function: getLabelValue
+ *
+ * Returns the value to be used for the label.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.getLabelValue = function(state)
+{
+ var graph = state.view.graph;
+ var value = graph.getLabel(state.cell);
+
+ if (!graph.isHtmlLabel(state.cell) && !mxUtils.isNode(value) &&
+ graph.dialect != mxConstants.DIALECT_SVG && value != null)
+ {
+ value = mxUtils.htmlEntities(value, false);
+ }
+
+ return value;
+};
+
+/**
+ * Function: createLabel
+ *
+ * Creates the label for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.createLabel = function(state, value)
+{
+ var graph = state.view.graph;
+ var isEdge = graph.getModel().isEdge(state.cell);
+
+ if (state.style[mxConstants.STYLE_FONTSIZE] > 0 ||
+ state.style[mxConstants.STYLE_FONTSIZE] == null)
+ {
+ // Avoids using DOM node for empty labels
+ var isForceHtml = (graph.isHtmlLabel(state.cell) ||
+ (value != null && mxUtils.isNode(value))) &&
+ graph.dialect == mxConstants.DIALECT_SVG;
+
+ state.text = new mxText(value, new mxRectangle(),
+ (state.style[mxConstants.STYLE_ALIGN] ||
+ mxConstants.ALIGN_CENTER),
+ graph.getVerticalAlign(state),
+ state.style[mxConstants.STYLE_FONTCOLOR],
+ state.style[mxConstants.STYLE_FONTFAMILY],
+ state.style[mxConstants.STYLE_FONTSIZE],
+ state.style[mxConstants.STYLE_FONTSTYLE],
+ state.style[mxConstants.STYLE_SPACING],
+ state.style[mxConstants.STYLE_SPACING_TOP],
+ state.style[mxConstants.STYLE_SPACING_RIGHT],
+ state.style[mxConstants.STYLE_SPACING_BOTTOM],
+ state.style[mxConstants.STYLE_SPACING_LEFT],
+ state.style[mxConstants.STYLE_HORIZONTAL],
+ state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
+ state.style[mxConstants.STYLE_LABEL_BORDERCOLOR],
+ graph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),
+ graph.isLabelClipped(state.cell),
+ state.style[mxConstants.STYLE_OVERFLOW],
+ state.style[mxConstants.STYLE_LABEL_PADDING]);
+ state.text.opacity = state.style[mxConstants.STYLE_TEXT_OPACITY];
+
+ state.text.dialect = (isForceHtml) ?
+ mxConstants.DIALECT_STRICTHTML :
+ state.view.graph.dialect;
+ this.initializeLabel(state);
+
+ // Workaround for touch devices routing all events for a mouse
+ // gesture (down, move, up) via the initial DOM node. IE is even
+ // worse in that it redirects the event via the initial DOM node
+ // but the event source is the node under the mouse, so we need
+ // to check if this is the case and force getCellAt for the
+ // subsequent mouseMoves and the final mouseUp.
+ var forceGetCell = false;
+
+ var getState = function(evt)
+ {
+ var result = state;
+
+ if (mxClient.IS_TOUCH || forceGetCell)
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+
+ // Dispatches the drop event to the graph which
+ // consumes and executes the source function
+ var pt = mxUtils.convertPoint(graph.container, x, y);
+ result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return result;
+ };
+
+ // TODO: Add handling for gestures
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(state.text.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, state));
+ forceGetCell = graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG';
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, getState(evt)));
+ forceGetCell = false;
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.dblClick(evt, state.cell);
+ mxEvent.consume(evt);
+ }
+ })
+ );
+ }
+};
+
+/**
+ * Function: initializeLabel
+ *
+ * Initiailzes the label with a suitable container.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label should be initialized.
+ */
+mxCellRenderer.prototype.initializeLabel = function(state)
+{
+ var graph = state.view.graph;
+
+ if (state.text.dialect != mxConstants.DIALECT_SVG)
+ {
+ // Adds the text to the container if the dialect is not SVG and we
+ // have an SVG-based browser which doesn't support foreignObjects
+ if (mxClient.IS_SVG && mxClient.NO_FO)
+ {
+ state.text.init(graph.container);
+ }
+ else if (mxUtils.isVml(state.view.getDrawPane()))
+ {
+ if (state.shape.label != null)
+ {
+ state.text.init(state.shape.label);
+ }
+ else
+ {
+ state.text.init(state.shape.node);
+ }
+ }
+ }
+
+ if (state.text.node == null)
+ {
+ state.text.init(state.view.getDrawPane());
+
+ if (state.shape != null && state.text != null)
+ {
+ state.shape.node.parentNode.insertBefore(
+ state.text.node, state.shape.node.nextSibling);
+ }
+ }
+};
+
+/**
+ * Function: createCellOverlays
+ *
+ * Creates the actual shape for showing the overlay for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the overlay should be created.
+ */
+mxCellRenderer.prototype.createCellOverlays = function(state)
+{
+ var graph = state.view.graph;
+ var overlays = graph.getCellOverlays(state.cell);
+ var dict = null;
+
+ if (overlays != null)
+ {
+ dict = new mxDictionary();
+
+ for (var i = 0; i < overlays.length; i++)
+ {
+ var shape = (state.overlays != null) ? state.overlays.remove(overlays[i]) : null;
+
+ if (shape == null)
+ {
+ var tmp = new mxImageShape(new mxRectangle(),
+ overlays[i].image.src);
+ tmp.dialect = state.view.graph.dialect;
+ tmp.preserveImageAspect = false;
+ tmp.overlay = overlays[i];
+ this.initializeOverlay(state, tmp);
+ this.installCellOverlayListeners(state, overlays[i], tmp);
+
+ if (overlays[i].cursor != null)
+ {
+ tmp.node.style.cursor = overlays[i].cursor;
+ }
+
+ dict.put(overlays[i], tmp);
+ }
+ else
+ {
+ dict.put(overlays[i], shape);
+ }
+ }
+ }
+
+ // Removes unused
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ shape.destroy();
+ });
+ }
+
+ state.overlays = dict;
+};
+
+/**
+ * Function: initializeOverlay
+ *
+ * Initializes the given overlay.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the overlay should be created.
+ * overlay - <mxImageShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.initializeOverlay = function(state, overlay)
+{
+ overlay.init(state.view.getOverlayPane());
+};
+
+/**
+ * Function: installOverlayListeners
+ *
+ * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
+ * <mxShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape)
+{
+ var graph = state.view.graph;
+
+ mxEvent.addListener(shape.node, 'click', function (evt)
+ {
+ if (graph.isEditing())
+ {
+ graph.stopEditing(!graph.isInvokesStopCellEditing());
+ }
+
+ overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+ 'event', evt, 'cell', state.cell));
+ });
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+
+ mxEvent.addListener(shape.node, md, function (evt)
+ {
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(shape.node, mm, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, state));
+ });
+
+ if (mxClient.IS_TOUCH)
+ {
+ mxEvent.addListener(shape.node, 'touchend', function (evt)
+ {
+ overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+ 'event', evt, 'cell', state.cell));
+ });
+ }
+};
+
+/**
+ * Function: createControl
+ *
+ * Creates the control for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the control should be created.
+ */
+mxCellRenderer.prototype.createControl = function(state)
+{
+ var graph = state.view.graph;
+ var image = graph.getFoldingImage(state);
+
+ if (graph.foldingEnabled && image != null)
+ {
+ if (state.control == null)
+ {
+ var b = new mxRectangle(0, 0, image.width, image.height);
+ state.control = new mxImageShape(b, image.src);
+ state.control.dialect = graph.dialect;
+ state.control.preserveImageAspect = false;
+
+ this.initControl(state, state.control, true, function (evt)
+ {
+ if (graph.isEnabled())
+ {
+ var collapse = !graph.isCellCollapsed(state.cell);
+ graph.foldCells(collapse, false, [state.cell]);
+ mxEvent.consume(evt);
+ }
+ });
+ }
+ }
+ else if (state.control != null)
+ {
+ state.control.destroy();
+ state.control = null;
+ }
+};
+
+/**
+ * Function: initControl
+ *
+ * Initializes the given control and returns the corresponding DOM node.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the control should be initialized.
+ * control - <mxShape> to be initialized.
+ * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.
+ * clickHandler - Optional function to implement clicks on the control.
+ */
+mxCellRenderer.prototype.initControl = function(state, control, handleEvents, clickHandler)
+{
+ var graph = state.view.graph;
+
+ // In the special case where the label is in HTML and the display is SVG the image
+ // should go into the graph container directly in order to be clickable. Otherwise
+ // it is obscured by the HTML label that overlaps the cell.
+ var isForceHtml = graph.isHtmlLabel(state.cell) &&
+ mxClient.NO_FO &&
+ graph.dialect == mxConstants.DIALECT_SVG;
+
+ if (isForceHtml)
+ {
+ control.dialect = mxConstants.DIALECT_PREFERHTML;
+ control.init(graph.container);
+ control.node.style.zIndex = 1;
+ }
+ else
+ {
+ control.init(state.view.getOverlayPane());
+ }
+
+ var node = control.innerNode || control.node;
+
+ if (clickHandler)
+ {
+ if (graph.isEnabled())
+ {
+ node.style.cursor = 'pointer';
+ }
+
+ mxEvent.addListener(node, 'click', clickHandler);
+ }
+
+ if (handleEvents)
+ {
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+
+ mxEvent.addListener(node, md, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(node, mm, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
+ });
+ }
+
+ return node;
+};
+
+/**
+ * Function: isShapeEvent
+ *
+ * Returns true if the event is for the shape of the given state. This
+ * implementation always returns true.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isShapeEvent = function(state, evt)
+{
+ return true;
+};
+
+/**
+ * Function: isLabelEvent
+ *
+ * Returns true if the event is for the label of the given state. This
+ * implementation always returns true.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isLabelEvent = function(state, evt)
+{
+ return true;
+};
+
+/**
+ * Function: installListeners
+ *
+ * Installs the event listeners for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the event listeners should be isntalled.
+ */
+mxCellRenderer.prototype.installListeners = function(state)
+{
+ var graph = state.view.graph;
+
+ // Receives events from transparent backgrounds
+ if (graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ var events = 'all';
+
+ // Disabled fill-events on non-filled edges
+ if (graph.getModel().isEdge(state.cell) && state.shape.stroke != null &&
+ (state.shape.fill == null || state.shape.fill == mxConstants.NONE))
+ {
+ events = 'visibleStroke';
+ }
+
+ // Specifies the event-processing on the shape
+ if (state.shape.innerNode != null)
+ {
+ state.shape.innerNode.setAttribute('pointer-events', events);
+ }
+ else
+ {
+ state.shape.node.setAttribute('pointer-events', events);
+ }
+ }
+
+ // Workaround for touch devices routing all events for a mouse
+ // gesture (down, move, up) via the initial DOM node. Same for
+ // HTML images in all IE versions (VML images are working).
+ var getState = function(evt)
+ {
+ var result = state;
+
+ if ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH)
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+
+ // Dispatches the drop event to the graph which
+ // consumes and executes the source function
+ var pt = mxUtils.convertPoint(graph.container, x, y);
+ result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return result;
+ };
+
+ // Experimental support for two-finger pinch to resize cells
+ var gestureInProgress = false;
+
+ mxEvent.addListener(state.shape.node, 'gesturestart',
+ mxUtils.bind(this, function(evt)
+ {
+ // FIXME: Breaks encapsulation to reset the double
+ // tap event handling when gestures take place
+ graph.lastTouchTime = 0;
+
+ gestureInProgress = true;
+ mxEvent.consume(evt);
+ })
+ );
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(state.shape.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ // Redirects events from the "event-transparent" region of
+ // a swimlane to the graph. This is only required in HTML,
+ // SVG and VML do not fire mouse events on transparent
+ // backgrounds.
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : state));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ mxEvent.addListener(state.shape.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : getState(evt)));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ mxEvent.addListener(state.shape.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : getState(evt)));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ // Experimental handling for gestures. Double-tap handling is implemented
+ // in mxGraph.fireMouseEvent.
+ var dc = (mxClient.IS_TOUCH) ? 'gestureend' : 'dblclick';
+
+ mxEvent.addListener(state.shape.node, dc,
+ mxUtils.bind(this, function(evt)
+ {
+ gestureInProgress = false;
+
+ if (dc == 'gestureend')
+ {
+ // FIXME: Breaks encapsulation to reset the double
+ // tap event handling when gestures take place
+ graph.lastTouchTime = 0;
+
+ if (graph.gestureEnabled)
+ {
+ graph.handleGesture(state, evt);
+ mxEvent.consume(evt);
+ }
+ }
+ else if (this.isShapeEvent(state, evt))
+ {
+ graph.dblClick(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : state.cell);
+ mxEvent.consume(evt);
+ }
+ })
+ );
+};
+
+/**
+ * Function: redrawLabel
+ *
+ * Redraws the label for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label should be redrawn.
+ */
+mxCellRenderer.prototype.redrawLabel = function(state)
+{
+ var value = this.getLabelValue(state);
+
+ // FIXME: Add label always if HTML label and NO_FO
+ if (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0))
+ {
+ this.createLabel(state, value);
+ }
+ else if (state.text != null && (value == null || value.length == 0))
+ {
+ state.text.destroy();
+ state.text = null;
+ }
+
+ if (state.text != null)
+ {
+ var graph = state.view.graph;
+ var wrapping = graph.isWrapping(state.cell);
+ var clipping = graph.isLabelClipped(state.cell);
+ var bounds = this.getLabelBounds(state);
+
+ if (state.text.value != value || state.text.isWrapping != wrapping ||
+ state.text.isClipping != clipping || state.text.scale != state.view.scale ||
+ !state.text.bounds.equals(bounds))
+ {
+ state.text.value = value;
+ state.text.bounds = bounds;
+ state.text.scale = this.getTextScale(state);
+ state.text.isWrapping = wrapping;
+ state.text.isClipping = clipping;
+
+ state.text.redraw();
+ }
+ }
+};
+
+/**
+ * Function: getTextScale
+ *
+ * Returns the scaling used for the label of the given state
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label scale should be returned.
+ */
+mxCellRenderer.prototype.getTextScale = function(state)
+{
+ return state.view.scale;
+};
+
+/**
+ * Function: getLabelBounds
+ *
+ * Returns the bounds to be used to draw the label of the given state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label bounds should be returned.
+ */
+mxCellRenderer.prototype.getLabelBounds = function(state)
+{
+ var graph = state.view.graph;
+ var isEdge = graph.getModel().isEdge(state.cell);
+ var bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);
+
+ if (!isEdge)
+ {
+ bounds.x += state.x;
+ bounds.y += state.y;
+
+ // Minimum of 1 fixes alignment bug in HTML labels
+ bounds.width = Math.max(1, state.width);
+ bounds.height = Math.max(1, state.height);
+
+ if (graph.isSwimlane(state.cell))
+ {
+ var scale = graph.view.scale;
+ var size = graph.getStartSize(state.cell);
+
+ if (size.width > 0)
+ {
+ bounds.width = size.width * scale;
+ }
+ else if (size.height > 0)
+ {
+ bounds.height = size.height * scale;
+ }
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: redrawCellOverlays
+ *
+ * Redraws the overlays for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose overlays should be redrawn.
+ */
+mxCellRenderer.prototype.redrawCellOverlays = function(state)
+{
+ this.createCellOverlays(state);
+
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ var bounds = shape.overlay.getBounds(state);
+
+ if (shape.bounds == null || shape.scale != state.view.scale ||
+ !shape.bounds.equals(bounds))
+ {
+ shape.bounds = bounds;
+ shape.scale = state.view.scale;
+ shape.redraw();
+ }
+ });
+ }
+};
+
+/**
+ * Function: redrawControl
+ *
+ * Redraws the control for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose control should be redrawn.
+ */
+mxCellRenderer.prototype.redrawControl = function(state)
+{
+ if (state.control != null)
+ {
+ var bounds = this.getControlBounds(state);
+ var s = state.view.scale;
+
+ if (state.control.scale != s || !state.control.bounds.equals(bounds))
+ {
+ state.control.bounds = bounds;
+ state.control.scale = s;
+ state.control.redraw();
+ }
+ }
+};
+
+/**
+ * Function: getControlBounds
+ *
+ * Returns the bounds to be used to draw the control (folding icon) of the
+ * given state.
+ */
+mxCellRenderer.prototype.getControlBounds = function(state)
+{
+ if (state.control != null)
+ {
+ var oldScale = state.control.scale;
+ var w = state.control.bounds.width / oldScale;
+ var h = state.control.bounds.height / oldScale;
+ var s = state.view.scale;
+
+ return (state.view.graph.getModel().isEdge(state.cell)) ?
+ new mxRectangle(state.x + state.width / 2 - w / 2 * s,
+ state.y + state.height / 2 - h / 2 * s, w * s, h * s)
+ : new mxRectangle(state.x + w / 2 * s,
+ state.y + h / 2 * s, w * s, h * s);
+ }
+
+ return null;
+};
+
+/**
+ * Function: redraw
+ *
+ * Updates the bounds or points and scale of the shapes for the given cell
+ * state. This is called in mxGraphView.validatePoints as the last step of
+ * updating all cells.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shapes should be updated.
+ * force - Optional boolean that specifies if the cell should be reconfiured
+ * and redrawn without any additional checks.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be drawn into the DOM. If this is false then redraw and/or reconfigure
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.redraw = function(state, force, rendering)
+{
+ if (state.shape != null)
+ {
+ var model = state.view.graph.getModel();
+ var isEdge = model.isEdge(state.cell);
+ reconfigure = (force != null) ? force : false;
+
+ // Handles changes of the collapse icon
+ this.createControl(state);
+
+ // Handles changes to the order in the DOM
+ if (state.orderChanged || state.invalidOrder)
+ {
+ if (state.view.graph.ordered)
+ {
+ this.order(state);
+ }
+ else
+ {
+ // Assert state.cell is edge
+ this.orderEdge(state);
+ }
+
+ // Required to update inherited styles
+ reconfigure = state.orderChanged;
+ }
+
+ delete state.invalidOrder;
+ delete state.orderChanged;
+
+ // Checks if the style in the state is different from the style
+ // in the shape and re-applies the style if required
+ if (!reconfigure && !mxUtils.equalEntries(state.shape.style, state.style))
+ {
+ reconfigure = true;
+ }
+
+ // Reconfiures the shape after an order or style change
+ if (reconfigure)
+ {
+ this.configureShape(state);
+ state.shape.reconfigure();
+ }
+
+ // Redraws the cell if required
+ if (force || state.shape.bounds == null || state.shape.scale != state.view.scale ||
+ !state.shape.bounds.equals(state) ||
+ !mxUtils.equalPoints(state.shape.points, state.absolutePoints))
+ {
+ // FIXME: Move indicator color update into shape.redraw
+// var indicator = state.view.graph.getIndicatorColor(state);
+// if (indicator != null)
+// {
+// state.shape.indicatorColor = indicator;
+// }
+
+ if (state.absolutePoints != null)
+ {
+ state.shape.points = state.absolutePoints.slice();
+ }
+ else
+ {
+ state.shape.points = null;
+ }
+
+ state.shape.bounds = new mxRectangle(
+ state.x, state.y, state.width, state.height);
+ state.shape.scale = state.view.scale;
+
+ if (rendering == null || rendering)
+ {
+ state.shape.redraw();
+ }
+ else
+ {
+ state.shape.updateBoundingBox();
+ }
+ }
+
+ // Updates the text label, overlays and control
+ if (rendering == null || rendering)
+ {
+ this.redrawLabel(state);
+ this.redrawCellOverlays(state);
+ this.redrawControl(state);
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the shapes associated with the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shapes should be destroyed.
+ */
+mxCellRenderer.prototype.destroy = function(state)
+{
+ if (state.shape != null)
+ {
+ if (state.text != null)
+ {
+ state.text.destroy();
+ state.text = null;
+ }
+
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ shape.destroy();
+ });
+
+ state.overlays = null;
+ }
+
+ if (state.control != null)
+ {
+ state.control.destroy();
+ state.control = null;
+ }
+
+ state.shape.destroy();
+ state.shape = null;
+ }
+};
diff --git a/src/js/view/mxCellState.js b/src/js/view/mxCellState.js
new file mode 100644
index 0000000..7e7a3b0
--- /dev/null
+++ b/src/js/view/mxCellState.js
@@ -0,0 +1,375 @@
+/**
+ * $Id: mxCellState.js,v 1.42 2012-03-19 10:47:08 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellState
+ *
+ * Represents the current state of a cell in a given <mxGraphView>.
+ *
+ * For edges, the edge label position is stored in <absoluteOffset>.
+ *
+ * The size for oversize labels can be retrieved using the boundingBox property
+ * of the <text> field as shown below.
+ *
+ * (code)
+ * var bbox = (state.text != null) ? state.text.boundingBox : null;
+ * (end)
+ *
+ * Constructor: mxCellState
+ *
+ * Constructs a new object that represents the current state of the given
+ * cell in the specified view.
+ *
+ * Parameters:
+ *
+ * view - <mxGraphView> that contains the state.
+ * cell - <mxCell> that this state represents.
+ * style - Array of key, value pairs that constitute the style.
+ */
+function mxCellState(view, cell, style)
+{
+ this.view = view;
+ this.cell = cell;
+ this.style = style;
+
+ this.origin = new mxPoint();
+ this.absoluteOffset = new mxPoint();
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxCellState.prototype = new mxRectangle();
+mxCellState.prototype.constructor = mxCellState;
+
+/**
+ * Variable: view
+ *
+ * Reference to the enclosing <mxGraphView>.
+ */
+mxCellState.prototype.view = null;
+
+/**
+ * Variable: cell
+ *
+ * Reference to the <mxCell> that is represented by this state.
+ */
+mxCellState.prototype.cell = null;
+
+/**
+ * Variable: style
+ *
+ * Contains an array of key, value pairs that represent the style of the
+ * cell.
+ */
+mxCellState.prototype.style = null;
+
+/**
+ * Variable: invalid
+ *
+ * Specifies if the state is invalid. Default is true.
+ */
+mxCellState.prototype.invalid = true;
+
+/**
+ * Variable: invalidOrder
+ *
+ * Specifies if the cell has an invalid order. For internal use. Default is
+ * false.
+ */
+mxCellState.prototype.invalidOrder = false;
+
+/**
+ * Variable: orderChanged
+ *
+ * Specifies if the cell has changed order and the display needs to be
+ * updated.
+ */
+mxCellState.prototype.orderChanged = false;
+
+/**
+ * Variable: origin
+ *
+ * <mxPoint> that holds the origin for all child cells. Default is a new
+ * empty <mxPoint>.
+ */
+mxCellState.prototype.origin = null;
+
+/**
+ * Variable: absolutePoints
+ *
+ * Holds an array of <mxPoints> that represent the absolute points of an
+ * edge.
+ */
+mxCellState.prototype.absolutePoints = null;
+
+/**
+ * Variable: absoluteOffset
+ *
+ * <mxPoint> that holds the absolute offset. For edges, this is the
+ * absolute coordinates of the label position. For vertices, this is the
+ * offset of the label relative to the top, left corner of the vertex.
+ */
+mxCellState.prototype.absoluteOffset = null;
+
+/**
+ * Variable: visibleSourceState
+ *
+ * Caches the visible source terminal state.
+ */
+mxCellState.prototype.visibleSourceState = null;
+
+/**
+ * Variable: visibleTargetState
+ *
+ * Caches the visible target terminal state.
+ */
+mxCellState.prototype.visibleTargetState = null;
+
+/**
+ * Variable: terminalDistance
+ *
+ * Caches the distance between the end points for an edge.
+ */
+mxCellState.prototype.terminalDistance = 0;
+
+/**
+ * Variable: length
+ *
+ * Caches the length of an edge.
+ */
+mxCellState.prototype.length = 0;
+
+/**
+ * Variable: segments
+ *
+ * Array of numbers that represent the cached length of each segment of the
+ * edge.
+ */
+mxCellState.prototype.segments = null;
+
+/**
+ * Variable: shape
+ *
+ * Holds the <mxShape> that represents the cell graphically.
+ */
+mxCellState.prototype.shape = null;
+
+/**
+ * Variable: text
+ *
+ * Holds the <mxText> that represents the label of the cell. Thi smay be
+ * null if the cell has no label.
+ */
+mxCellState.prototype.text = null;
+
+/**
+ * Function: getPerimeterBounds
+ *
+ * Returns the <mxRectangle> that should be used as the perimeter of the
+ * cell.
+ *
+ * Parameters:
+ *
+ * border - Optional border to be added around the perimeter bounds.
+ * bounds - Optional <mxRectangle> to be used as the initial bounds.
+ */
+mxCellState.prototype.getPerimeterBounds = function (border, bounds)
+{
+ border = border || 0;
+ bounds = (bounds != null) ? bounds : new mxRectangle(this.x, this.y, this.width, this.height);
+
+ if (this.shape != null && this.shape.stencil != null)
+ {
+ var aspect = this.shape.stencil.computeAspect(this, bounds, null);
+
+ bounds.x = aspect.x;
+ bounds.y = aspect.y;
+ bounds.width = this.shape.stencil.w0 * aspect.width;
+ bounds.height = this.shape.stencil.h0 * aspect.height;
+ }
+
+ if (border != 0)
+ {
+ bounds.grow(border);
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: setAbsoluteTerminalPoint
+ *
+ * Sets the first or last point in <absolutePoints> depending on isSource.
+ *
+ * Parameters:
+ *
+ * point - <mxPoint> that represents the terminal point.
+ * isSource - Boolean that specifies if the first or last point should
+ * be assigned.
+ */
+mxCellState.prototype.setAbsoluteTerminalPoint = function (point, isSource)
+{
+ if (isSource)
+ {
+ if (this.absolutePoints == null)
+ {
+ this.absolutePoints = [];
+ }
+
+ if (this.absolutePoints.length == 0)
+ {
+ this.absolutePoints.push(point);
+ }
+ else
+ {
+ this.absolutePoints[0] = point;
+ }
+ }
+ else
+ {
+ if (this.absolutePoints == null)
+ {
+ this.absolutePoints = [];
+ this.absolutePoints.push(null);
+ this.absolutePoints.push(point);
+ }
+ else if (this.absolutePoints.length == 1)
+ {
+ this.absolutePoints.push(point);
+ }
+ else
+ {
+ this.absolutePoints[this.absolutePoints.length - 1] = point;
+ }
+ }
+};
+
+/**
+ * Function: setCursor
+ *
+ * Sets the given cursor on the shape and text shape.
+ */
+mxCellState.prototype.setCursor = function (cursor)
+{
+ if (this.shape != null)
+ {
+ this.shape.setCursor(cursor);
+ }
+
+ if (this.text != null)
+ {
+ this.text.setCursor(cursor);
+ }
+};
+
+/**
+ * Function: getVisibleTerminal
+ *
+ * Returns the visible source or target terminal cell.
+ *
+ * Parameters:
+ *
+ * source - Boolean that specifies if the source or target cell should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminal = function (source)
+{
+ var tmp = this.getVisibleTerminalState(source);
+
+ return (tmp != null) ? tmp.cell : null;
+};
+
+/**
+ * Function: getVisibleTerminalState
+ *
+ * Returns the visible source or target terminal state.
+ *
+ * Parameters:
+ *
+ * source - Boolean that specifies if the source or target state should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminalState = function (source)
+{
+ return (source) ? this.visibleSourceState : this.visibleTargetState;
+};
+
+/**
+ * Function: setVisibleTerminalState
+ *
+ * Sets the visible source or target terminal state.
+ *
+ * Parameters:
+ *
+ * terminalState - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the source or target state should be set.
+ */
+mxCellState.prototype.setVisibleTerminalState = function (terminalState, source)
+{
+ if (source)
+ {
+ this.visibleSourceState = terminalState;
+ }
+ else
+ {
+ this.visibleTargetState = terminalState;
+ }
+};
+
+/**
+ * Destructor: destroy
+ *
+ * Destroys the state and all associated resources.
+ */
+mxCellState.prototype.destroy = function ()
+{
+ this.view.graph.cellRenderer.destroy(this);
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxCellState.prototype.clone = function()
+{
+ var clone = new mxCellState(this.view, this.cell, this.style);
+
+ // Clones the absolute points
+ if (this.absolutePoints != null)
+ {
+ clone.absolutePoints = [];
+
+ for (var i = 0; i < this.absolutePoints.length; i++)
+ {
+ clone.absolutePoints[i] = this.absolutePoints[i].clone();
+ }
+ }
+
+ if (this.origin != null)
+ {
+ clone.origin = this.origin.clone();
+ }
+
+ if (this.absoluteOffset != null)
+ {
+ clone.absoluteOffset = this.absoluteOffset.clone();
+ }
+
+ if (this.boundingBox != null)
+ {
+ clone.boundingBox = this.boundingBox.clone();
+ }
+
+ clone.terminalDistance = this.terminalDistance;
+ clone.segments = this.segments;
+ clone.length = this.length;
+ clone.x = this.x;
+ clone.y = this.y;
+ clone.width = this.width;
+ clone.height = this.height;
+
+ return clone;
+};
diff --git a/src/js/view/mxCellStatePreview.js b/src/js/view/mxCellStatePreview.js
new file mode 100644
index 0000000..b853748
--- /dev/null
+++ b/src/js/view/mxCellStatePreview.js
@@ -0,0 +1,223 @@
+/**
+ * $Id: mxCellStatePreview.js,v 1.6 2012-10-26 07:19:11 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxCellStatePreview
+ *
+ * Implements a live preview for moving cells.
+ *
+ * Constructor: mxCellStatePreview
+ *
+ * Constructs a move preview for the given graph.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellStatePreview(graph)
+{
+ this.graph = graph;
+ this.deltas = new Object();
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.graph = null;
+
+/**
+ * Variable: deltas
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.deltas = null;
+
+/**
+ * Variable: count
+ *
+ * Contains the number of entries in the map.
+ */
+mxCellStatePreview.prototype.count = 0;
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if this contains no entries.
+ */
+mxCellStatePreview.prototype.isEmpty = function()
+{
+ return this.count == 0;
+};
+
+/**
+ * Function: moveState
+ */
+mxCellStatePreview.prototype.moveState = function(state, dx, dy, add, includeEdges)
+{
+ add = (add != null) ? add : true;
+ includeEdges = (includeEdges != null) ? includeEdges : true;
+ var id = mxCellPath.create(state.cell);
+ var delta = this.deltas[id];
+
+ if (delta == null)
+ {
+ delta = new mxPoint(dx, dy);
+ this.deltas[id] = delta;
+ this.count++;
+ }
+ else
+ {
+ if (add)
+ {
+ delta.X += dx;
+ delta.Y += dy;
+ }
+ else
+ {
+ delta.X = dx;
+ delta.Y = dy;
+ }
+ }
+
+ if (includeEdges)
+ {
+ this.addEdges(state);
+ }
+
+ return delta;
+};
+
+/**
+ * Function: show
+ */
+mxCellStatePreview.prototype.show = function(visitor)
+{
+ var model = this.graph.getModel();
+ var root = model.getRoot();
+
+ // Translates the states in step
+ for (var id in this.deltas)
+ {
+ var cell = mxCellPath.resolve(root, id);
+ var state = this.graph.view.getState(cell);
+ var delta = this.deltas[id];
+ var parentState = this.graph.view.getState(
+ model.getParent(cell));
+ this.translateState(parentState, state, delta.x, delta.y);
+ }
+
+ // Revalidates the states in step
+ for (var id in this.deltas)
+ {
+ var cell = mxCellPath.resolve(root, id);
+ var state = this.graph.view.getState(cell);
+ var delta = this.deltas[id];
+ var parentState = this.graph.view.getState(
+ model.getParent(cell));
+ this.revalidateState(parentState, state, delta.x, delta.y, visitor);
+ }
+};
+
+/**
+ * Function: translateState
+ */
+mxCellStatePreview.prototype.translateState = function(parentState, state, dx, dy)
+{
+ if (state != null)
+ {
+ var model = this.graph.getModel();
+
+ if (model.isVertex(state.cell))
+ {
+ // LATER: Use hashtable to store initial state bounds
+ state.invalid = true;
+ this.graph.view.validateBounds(parentState, state.cell);
+ var geo = model.getGeometry(state.cell);
+ var id = mxCellPath.create(state.cell);
+
+ // Moves selection cells and non-relative vertices in
+ // the first phase so that edge terminal points will
+ // be updated in the second phase
+ if ((dx != 0 || dy != 0) && geo != null &&
+ (!geo.relative || this.deltas[id] != null))
+ {
+ state.x += dx;
+ state.y += dy;
+ }
+ }
+
+ var childCount = model.getChildCount(state.cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.translateState(state, this.graph.view.getState(
+ model.getChildAt(state.cell, i)), dx, dy);
+ }
+ }
+};
+
+/**
+ * Function: revalidateState
+ */
+mxCellStatePreview.prototype.revalidateState = function(parentState, state, dx, dy, visitor)
+{
+ if (state != null)
+ {
+ // Updates the edge terminal points and restores the
+ // (relative) positions of any (relative) children
+ state.invalid = true;
+ this.graph.view.validatePoints(parentState, state.cell);
+
+ // Moves selection vertices which are relative
+ var id = mxCellPath.create(state.cell);
+ var model = this.graph.getModel();
+ var geo = this.graph.getCellGeometry(state.cell);
+
+ if ((dx != 0 || dy != 0) && geo != null && geo.relative &&
+ model.isVertex(state.cell) && (parentState == null ||
+ model.isVertex(parentState.cell) || this.deltas[id] != null))
+ {
+ state.x += dx;
+ state.y += dy;
+
+ this.graph.cellRenderer.redraw(state);
+ }
+
+ // Invokes the visitor on the given state
+ if (visitor != null)
+ {
+ visitor(state);
+ }
+
+ var childCount = model.getChildCount(state.cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.revalidateState(state, this.graph.view.getState(model.getChildAt(
+ state.cell, i)), dx, dy, visitor);
+ }
+ }
+};
+
+/**
+ * Function: addEdges
+ */
+mxCellStatePreview.prototype.addEdges = function(state)
+{
+ var model = this.graph.getModel();
+ var edgeCount = model.getEdgeCount(state.cell);
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var s = this.graph.view.getState(model.getEdgeAt(state.cell, i));
+
+ if (s != null)
+ {
+ this.moveState(s, 0, 0);
+ }
+ }
+};
diff --git a/src/js/view/mxConnectionConstraint.js b/src/js/view/mxConnectionConstraint.js
new file mode 100644
index 0000000..70f457f
--- /dev/null
+++ b/src/js/view/mxConnectionConstraint.js
@@ -0,0 +1,42 @@
+/**
+ * $Id: mxConnectionConstraint.js,v 1.2 2010-04-29 09:33:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxConnectionConstraint
+ *
+ * Defines an object that contains the constraints about how to connect one
+ * side of an edge to its terminal.
+ *
+ * Constructor: mxConnectionConstraint
+ *
+ * Constructs a new connection constraint for the given point and boolean
+ * arguments.
+ *
+ * Parameters:
+ *
+ * point - Optional <mxPoint> that specifies the fixed location of the point
+ * in relative coordinates. Default is null.
+ * perimeter - Optional boolean that specifies if the fixed point should be
+ * projected onto the perimeter of the terminal. Default is true.
+ */
+function mxConnectionConstraint(point, perimeter)
+{
+ this.point = point;
+ this.perimeter = (perimeter != null) ? perimeter : true;
+};
+
+/**
+ * Variable: point
+ *
+ * <mxPoint> that specifies the fixed location of the connection point.
+ */
+mxConnectionConstraint.prototype.point = null;
+
+/**
+ * Variable: perimeter
+ *
+ * Boolean that specifies if the point should be projected onto the perimeter
+ * of the terminal.
+ */
+mxConnectionConstraint.prototype.perimeter = null;
diff --git a/src/js/view/mxEdgeStyle.js b/src/js/view/mxEdgeStyle.js
new file mode 100644
index 0000000..41493d6
--- /dev/null
+++ b/src/js/view/mxEdgeStyle.js
@@ -0,0 +1,1302 @@
+/**
+ * $Id: mxEdgeStyle.js,v 1.68 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxEdgeStyle =
+{
+ /**
+ * Class: mxEdgeStyle
+ *
+ * Provides various edge styles to be used as the values for
+ * <mxConstants.STYLE_EDGE> in a cell style.
+ *
+ * Example:
+ *
+ * (code)
+ * var style = stylesheet.getDefaultEdgeStyle();
+ * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
+ * (end)
+ *
+ * Sets the default edge style to <ElbowConnector>.
+ *
+ * Custom edge style:
+ *
+ * To write a custom edge style, a function must be added to the mxEdgeStyle
+ * object as follows:
+ *
+ * (code)
+ * mxEdgeStyle.MyStyle = function(state, source, target, points, result)
+ * {
+ * if (source != null && target != null)
+ * {
+ * var pt = new mxPoint(target.getCenterX(), source.getCenterY());
+ *
+ * if (mxUtils.contains(source, pt.x, pt.y))
+ * {
+ * pt.y = source.y + source.height;
+ * }
+ *
+ * result.push(pt);
+ * }
+ * };
+ * (end)
+ *
+ * In the above example, a right angle is created using a point on the
+ * horizontal center of the target vertex and the vertical center of the source
+ * vertex. The code checks if that point intersects the source vertex and makes
+ * the edge straight if it does. The point is then added into the result array,
+ * which acts as the return value of the function.
+ *
+ * The new edge style should then be registered in the <mxStyleRegistry> as follows:
+ * (code)
+ * mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);
+ * (end)
+ *
+ * The custom edge style above can now be used in a specific edge as follows:
+ *
+ * (code)
+ * model.setStyle(edge, 'edgeStyle=myEdgeStyle');
+ * (end)
+ *
+ * Note that the key of the <mxStyleRegistry> entry for the function should
+ * be used in string values, unless <mxGraphView.allowEval> is true, in
+ * which case you can also use mxEdgeStyle.MyStyle for the value in the
+ * cell style above.
+ *
+ * Or it can be used for all edges in the graph as follows:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultEdgeStyle();
+ * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;
+ * (end)
+ *
+ * Note that the object can be used directly when programmatically setting
+ * the value, but the key in the <mxStyleRegistry> should be used when
+ * setting the value via a key, value pair in a cell style.
+ *
+ * Function: EntityRelation
+ *
+ * Implements an entity relation style for edges (as used in database
+ * schema diagrams). At the time the function is called, the result
+ * array contains a placeholder (null) for the first absolute point,
+ * that is, the point where the edge and source terminal are connected.
+ * The implementation of the style then adds all intermediate waypoints
+ * except for the last point, that is, the connection point between the
+ * edge and the target terminal. The first ant the last point in the
+ * result array are then replaced with mxPoints that take into account
+ * the terminal's perimeter and next point on the edge.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the edge to be updated.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ * points - List of relative control points.
+ * result - Array of <mxPoints> that represent the actual points of the
+ * edge.
+ */
+ EntityRelation: function (state, source, target, points, result)
+ {
+ var view = state.view;
+ var graph = view.graph;
+ var segment = mxUtils.getValue(state.style,
+ mxConstants.STYLE_SEGMENT,
+ mxConstants.ENTITY_SEGMENT) * view.scale;
+
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ var isSourceLeft = false;
+
+ if (p0 != null)
+ {
+ source = new mxCellState();
+ source.x = p0.x;
+ source.y = p0.y;
+ }
+ else if (source != null)
+ {
+ var constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE);
+
+ if (constraint != mxConstants.DIRECTION_MASK_NONE)
+ {
+ isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+ }
+ else
+ {
+ var sourceGeometry = graph.getCellGeometry(source.cell);
+
+ if (sourceGeometry.relative)
+ {
+ isSourceLeft = sourceGeometry.x <= 0.5;
+ }
+ else if (target != null)
+ {
+ isSourceLeft = target.x + target.width < source.x;
+ }
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ var isTargetLeft = true;
+
+ if (pe != null)
+ {
+ target = new mxCellState();
+ target.x = pe.x;
+ target.y = pe.y;
+ }
+ else if (target != null)
+ {
+ var constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE);
+
+ if (constraint != mxConstants.DIRECTION_MASK_NONE)
+ {
+ isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+ }
+ else
+ {
+ var targetGeometry = graph.getCellGeometry(target.cell);
+
+ if (targetGeometry.relative)
+ {
+ isTargetLeft = targetGeometry.x <= 0.5;
+ }
+ else if (source != null)
+ {
+ isTargetLeft = source.x + source.width < target.x;
+ }
+ }
+ }
+
+ if (source != null && target != null)
+ {
+ var x0 = (isSourceLeft) ? source.x : source.x + source.width;
+ var y0 = view.getRoutingCenterY(source);
+
+ var xe = (isTargetLeft) ? target.x : target.x + target.width;
+ var ye = view.getRoutingCenterY(target);
+
+ var seg = segment;
+
+ var dx = (isSourceLeft) ? -seg : seg;
+ var dep = new mxPoint(x0 + dx, y0);
+
+ dx = (isTargetLeft) ? -seg : seg;
+ var arr = new mxPoint(xe + dx, ye);
+
+ // Adds intermediate points if both go out on same side
+ if (isSourceLeft == isTargetLeft)
+ {
+ var x = (isSourceLeft) ?
+ Math.min(x0, xe)-segment :
+ Math.max(x0, xe)+segment;
+
+ result.push(new mxPoint(x, y0));
+ result.push(new mxPoint(x, ye));
+ }
+ else if ((dep.x < arr.x) == isSourceLeft)
+ {
+ var midY = y0 + (ye - y0) / 2;
+
+ result.push(dep);
+ result.push(new mxPoint(dep.x, midY));
+ result.push(new mxPoint(arr.x, midY));
+ result.push(arr);
+ }
+ else
+ {
+ result.push(dep);
+ result.push(arr);
+ }
+ }
+ },
+
+ /**
+ * Function: Loop
+ *
+ * Implements a self-reference, aka. loop.
+ */
+ Loop: function (state, source, target, points, result)
+ {
+ if (source != null)
+ {
+ var view = state.view;
+ var graph = view.graph;
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+
+ if (pt != null)
+ {
+ pt = view.transformControlPoint(state, pt);
+
+ if (mxUtils.contains(source, pt.x, pt.y))
+ {
+ pt = null;
+ }
+ }
+
+ var x = 0;
+ var dx = 0;
+ var y = 0;
+ var dy = 0;
+
+ var seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT,
+ graph.gridSize) * view.scale;
+ var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,
+ mxConstants.DIRECTION_WEST);
+
+ if (dir == mxConstants.DIRECTION_NORTH ||
+ dir == mxConstants.DIRECTION_SOUTH)
+ {
+ x = view.getRoutingCenterX(source);
+ dx = seg;
+ }
+ else
+ {
+ y = view.getRoutingCenterY(source);
+ dy = seg;
+ }
+
+ if (pt == null ||
+ pt.x < source.x ||
+ pt.x > source.x + source.width)
+ {
+ if (pt != null)
+ {
+ x = pt.x;
+ dy = Math.max(Math.abs(y - pt.y), dy);
+ }
+ else
+ {
+ if (dir == mxConstants.DIRECTION_NORTH)
+ {
+ y = source.y - 2 * dx;
+ }
+ else if (dir == mxConstants.DIRECTION_SOUTH)
+ {
+ y = source.y + source.height + 2 * dx;
+ }
+ else if (dir == mxConstants.DIRECTION_EAST)
+ {
+ x = source.x - 2 * dy;
+ }
+ else
+ {
+ x = source.x + source.width + 2 * dy;
+ }
+ }
+ }
+ else if (pt != null)
+ {
+ x = view.getRoutingCenterX(source);
+ dx = Math.max(Math.abs(x - pt.x), dy);
+ y = pt.y;
+ dy = 0;
+ }
+
+ result.push(new mxPoint(x - dx, y - dy));
+ result.push(new mxPoint(x + dx, y + dy));
+ }
+ },
+
+ /**
+ * Function: ElbowConnector
+ *
+ * Uses either <SideToSide> or <TopToBottom> depending on the horizontal
+ * flag in the cell style. <SideToSide> is used if horizontal is true or
+ * unspecified. See <EntityRelation> for a description of the
+ * parameters.
+ */
+ ElbowConnector: function (state, source, target, points, result)
+ {
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+
+ var vertical = false;
+ var horizontal = false;
+
+ if (source != null && target != null)
+ {
+ if (pt != null)
+ {
+ var left = Math.min(source.x, target.x);
+ var right = Math.max(source.x + source.width,
+ target.x + target.width);
+
+ var top = Math.min(source.y, target.y);
+ var bottom = Math.max(source.y + source.height,
+ target.y + target.height);
+
+ pt = state.view.transformControlPoint(state, pt);
+
+ vertical = pt.y < top || pt.y > bottom;
+ horizontal = pt.x < left || pt.x > right;
+ }
+ else
+ {
+ var left = Math.max(source.x, target.x);
+ var right = Math.min(source.x + source.width,
+ target.x + target.width);
+
+ vertical = left == right;
+
+ if (!vertical)
+ {
+ var top = Math.max(source.y, target.y);
+ var bottom = Math.min(source.y + source.height,
+ target.y + target.height);
+
+ horizontal = top == bottom;
+ }
+ }
+ }
+
+ if (!horizontal && (vertical ||
+ state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL))
+ {
+ mxEdgeStyle.TopToBottom(state, source, target, points, result);
+ }
+ else
+ {
+ mxEdgeStyle.SideToSide(state, source, target, points, result);
+ }
+ },
+
+ /**
+ * Function: SideToSide
+ *
+ * Implements a vertical elbow edge. See <EntityRelation> for a description
+ * of the parameters.
+ */
+ SideToSide: function (state, source, target, points, result)
+ {
+ var view = state.view;
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ if (pt != null)
+ {
+ pt = view.transformControlPoint(state, pt);
+ }
+
+ if (p0 != null)
+ {
+ source = new mxCellState();
+ source.x = p0.x;
+ source.y = p0.y;
+ }
+
+ if (pe != null)
+ {
+ target = new mxCellState();
+ target.x = pe.x;
+ target.y = pe.y;
+ }
+
+ if (source != null && target != null)
+ {
+ var l = Math.max(source.x, target.x);
+ var r = Math.min(source.x + source.width,
+ target.x + target.width);
+
+ var x = (pt != null) ? pt.x : r + (l - r) / 2;
+
+ var y1 = view.getRoutingCenterY(source);
+ var y2 = view.getRoutingCenterY(target);
+
+ if (pt != null)
+ {
+ if (pt.y >= source.y && pt.y <= source.y + source.height)
+ {
+ y1 = pt.y;
+ }
+
+ if (pt.y >= target.y && pt.y <= target.y + target.height)
+ {
+ y2 = pt.y;
+ }
+ }
+
+ if (!mxUtils.contains(target, x, y1) &&
+ !mxUtils.contains(source, x, y1))
+ {
+ result.push(new mxPoint(x, y1));
+ }
+
+ if (!mxUtils.contains(target, x, y2) &&
+ !mxUtils.contains(source, x, y2))
+ {
+ result.push(new mxPoint(x, y2));
+ }
+
+ if (result.length == 1)
+ {
+ if (pt != null)
+ {
+ if (!mxUtils.contains(target, x, pt.y) &&
+ !mxUtils.contains(source, x, pt.y))
+ {
+ result.push(new mxPoint(x, pt.y));
+ }
+ }
+ else
+ {
+ var t = Math.max(source.y, target.y);
+ var b = Math.min(source.y + source.height,
+ target.y + target.height);
+
+ result.push(new mxPoint(x, t + (b - t) / 2));
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: TopToBottom
+ *
+ * Implements a horizontal elbow edge. See <EntityRelation> for a
+ * description of the parameters.
+ */
+ TopToBottom: function(state, source, target, points, result)
+ {
+ var view = state.view;
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ if (pt != null)
+ {
+ pt = view.transformControlPoint(state, pt);
+ }
+
+ if (p0 != null)
+ {
+ source = new mxCellState();
+ source.x = p0.x;
+ source.y = p0.y;
+ }
+
+ if (pe != null)
+ {
+ target = new mxCellState();
+ target.x = pe.x;
+ target.y = pe.y;
+ }
+
+ if (source != null && target != null)
+ {
+ var t = Math.max(source.y, target.y);
+ var b = Math.min(source.y + source.height,
+ target.y + target.height);
+
+ var x = view.getRoutingCenterX(source);
+
+ if (pt != null &&
+ pt.x >= source.x &&
+ pt.x <= source.x + source.width)
+ {
+ x = pt.x;
+ }
+
+ var y = (pt != null) ? pt.y : b + (t - b) / 2;
+
+ if (!mxUtils.contains(target, x, y) &&
+ !mxUtils.contains(source, x, y))
+ {
+ result.push(new mxPoint(x, y));
+ }
+
+ if (pt != null &&
+ pt.x >= target.x &&
+ pt.x <= target.x + target.width)
+ {
+ x = pt.x;
+ }
+ else
+ {
+ x = view.getRoutingCenterX(target);
+ }
+
+ if (!mxUtils.contains(target, x, y) &&
+ !mxUtils.contains(source, x, y))
+ {
+ result.push(new mxPoint(x, y));
+ }
+
+ if (result.length == 1)
+ {
+ if (pt != null && result.length == 1)
+ {
+ if (!mxUtils.contains(target, pt.x, y) &&
+ !mxUtils.contains(source, pt.x, y))
+ {
+ result.push(new mxPoint(pt.x, y));
+ }
+ }
+ else
+ {
+ var l = Math.max(source.x, target.x);
+ var r = Math.min(source.x + source.width,
+ target.x + target.width);
+
+ result.push(new mxPoint(l + (r - l) / 2, y));
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: SegmentConnector
+ *
+ * Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>
+ * as an interactive handler for this style.
+ */
+ SegmentConnector: function(state, source, target, hints, result)
+ {
+ // Creates array of all way- and terminalpoints
+ var pts = state.absolutePoints;
+ var horizontal = true;
+ var hint = null;
+
+ // Adds the first point
+ var pt = pts[0];
+
+ if (pt == null && source != null)
+ {
+ pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
+ }
+ else if (pt != null)
+ {
+ pt = pt.clone();
+ }
+
+ var lastInx = pts.length - 1;
+
+ // Adds the waypoints
+ if (hints != null && hints.length > 0)
+ {
+ hint = state.view.transformControlPoint(state, hints[0]);
+
+ var currentTerm = source;
+ var currentPt = pts[0];
+ var hozChan = false;
+ var vertChan = false;
+ var currentHint = hint;
+ var hintsLen = hints.length;
+
+ for (var i = 0; i < 2; i++)
+ {
+ var fixedVertAlign = currentPt != null && currentPt.x == currentHint.x;
+ var fixedHozAlign = currentPt != null && currentPt.y == currentHint.y;
+ var inHozChan = currentTerm != null && (currentHint.y >= currentTerm.y &&
+ currentHint.y <= currentTerm.y + currentTerm.height);
+ var inVertChan = currentTerm != null && (currentHint.x >= currentTerm.x &&
+ currentHint.x <= currentTerm.x + currentTerm.width);
+
+ hozChan = fixedHozAlign || (currentPt == null && inHozChan);
+ vertChan = fixedVertAlign || (currentPt == null && inVertChan);
+
+ if (currentPt != null && (!fixedHozAlign && !fixedVertAlign) && (inHozChan || inVertChan))
+ {
+ horizontal = inHozChan ? false : true;
+ break;
+ }
+
+ if (vertChan || hozChan)
+ {
+ horizontal = hozChan;
+
+ if (i == 1)
+ {
+ // Work back from target end
+ horizontal = hints.length % 2 == 0 ? hozChan : vertChan;
+ }
+
+ break;
+ }
+
+ currentTerm = target;
+ currentPt = pts[lastInx];
+ currentHint = state.view.transformControlPoint(state, hints[hintsLen - 1]);
+ }
+
+ if (horizontal && ((pts[0] != null && pts[0].y != hint.y) ||
+ (pts[0] == null && source != null &&
+ (hint.y < source.y || hint.y > source.y + source.height))))
+ {
+ result.push(new mxPoint(pt.x, hint.y));
+ }
+ else if (!horizontal && ((pts[0] != null && pts[0].x != hint.x) ||
+ (pts[0] == null && source != null &&
+ (hint.x < source.x || hint.x > source.x + source.width))))
+ {
+ result.push(new mxPoint(hint.x, pt.y));
+ }
+
+ if (horizontal)
+ {
+ pt.y = hint.y;
+ }
+ else
+ {
+ pt.x = hint.x;
+ }
+
+ for (var i = 0; i < hints.length; i++)
+ {
+ horizontal = !horizontal;
+ hint = state.view.transformControlPoint(state, hints[i]);
+
+// mxLog.show();
+// mxLog.debug('hint', i, hint.x, hint.y);
+
+ if (horizontal)
+ {
+ pt.y = hint.y;
+ }
+ else
+ {
+ pt.x = hint.x;
+ }
+
+ result.push(pt.clone());
+ }
+ }
+ else
+ {
+ hint = pt;
+ // FIXME: First click in connect preview toggles orientation
+ horizontal = true;
+ }
+
+ // Adds the last point
+ pt = pts[lastInx];
+
+ if (pt == null && target != null)
+ {
+ pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
+ }
+
+ if (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||
+ (pts[lastInx] == null && target != null &&
+ (hint.y < target.y || hint.y > target.y + target.height))))
+ {
+ result.push(new mxPoint(pt.x, hint.y));
+ }
+ else if (!horizontal && ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||
+ (pts[lastInx] == null && target != null &&
+ (hint.x < target.x || hint.x > target.x + target.width))))
+ {
+ result.push(new mxPoint(hint.x, pt.y));
+ }
+
+ // Removes bends inside the source terminal for floating ports
+ if (pts[0] == null && source != null)
+ {
+ while (result.length > 1 && mxUtils.contains(source, result[1].x, result[1].y))
+ {
+ result = result.splice(1, 1);
+ }
+ }
+
+ // Removes bends inside the target terminal
+ if (pts[lastInx] == null && target != null)
+ {
+ while (result.length > 1 && mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y))
+ {
+ result = result.splice(result.length - 1, 1);
+ }
+ }
+
+ },
+
+ orthBuffer: 10,
+
+ dirVectors: [ [ -1, 0 ],
+ [ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ], [ 0, -1 ], [ 1, 0 ] ],
+
+ wayPoints1: [ [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0],
+ [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0] ],
+
+ routePatterns: [
+ [ [ 513, 2308, 2081, 2562 ], [ 513, 1090, 514, 2184, 2114, 2561 ],
+ [ 513, 1090, 514, 2564, 2184, 2562 ],
+ [ 513, 2308, 2561, 1090, 514, 2568, 2308 ] ],
+ [ [ 514, 1057, 513, 2308, 2081, 2562 ], [ 514, 2184, 2114, 2561 ],
+ [ 514, 2184, 2562, 1057, 513, 2564, 2184 ],
+ [ 514, 1057, 513, 2568, 2308, 2561 ] ],
+ [ [ 1090, 514, 1057, 513, 2308, 2081, 2562 ], [ 2114, 2561 ],
+ [ 1090, 2562, 1057, 513, 2564, 2184 ],
+ [ 1090, 514, 1057, 513, 2308, 2561, 2568 ] ],
+ [ [ 2081, 2562 ], [ 1057, 513, 1090, 514, 2184, 2114, 2561 ],
+ [ 1057, 513, 1090, 514, 2184, 2562, 2564 ],
+ [ 1057, 2561, 1090, 514, 2568, 2308 ] ] ],
+
+ inlineRoutePatterns: [
+ [ null, [ 2114, 2568 ], null, null ],
+ [ null, [ 514, 2081, 2114, 2568 ] , null, null ],
+ [ null, [ 2114, 2561 ], null, null ],
+ [ [ 2081, 2562 ], [ 1057, 2114, 2568 ],
+ [ 2184, 2562 ],
+ null ] ],
+ vertexSeperations: [],
+
+ limits: [
+ [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+ [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],
+
+ LEFT_MASK: 32,
+
+ TOP_MASK: 64,
+
+ RIGHT_MASK: 128,
+
+ BOTTOM_MASK: 256,
+
+ LEFT: 1,
+
+ TOP: 2,
+
+ RIGHT: 4,
+
+ BOTTOM: 8,
+
+ // TODO remove magic numbers
+ SIDE_MASK: 480,
+ //mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK
+ //| mxEdgeStyle.BOTTOM_MASK,
+
+ CENTER_MASK: 512,
+
+ SOURCE_MASK: 1024,
+
+ TARGET_MASK: 2048,
+
+ VERTEX_MASK: 3072,
+ // mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,
+
+ /**
+ * Function: OrthConnector
+ *
+ * Implements a local orthogonal router between the given
+ * cells.
+ */
+ OrthConnector: function(state, source, target, points, result)
+ {
+ var graph = state.view.graph;
+ var sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell);
+ var targetEdge = target == null ? false : graph.getModel().isEdge(target.cell);
+
+ if ((points != null && points.length > 0) || (sourceEdge) || (targetEdge))
+ {
+ mxEdgeStyle.SegmentConnector(state, source, target, points, result);
+ return;
+ }
+
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ var sourceX = source != null ? source.x : p0.x;
+ var sourceY = source != null ? source.y : p0.y;
+ var sourceWidth = source != null ? source.width : 1;
+ var sourceHeight = source != null ? source.height : 1;
+
+ var targetX = target != null ? target.x : pe.x;
+ var targetY = target != null ? target.y : pe.y;
+ var targetWidth = target != null ? target.width : 1;
+ var targetHeight = target != null ? target.height : 1;
+
+ var scaledOrthBuffer = state.view.scale * mxEdgeStyle.orthBuffer;
+ // Determine the side(s) of the source and target vertices
+ // that the edge may connect to
+ // portConstraint [source, target]
+ var portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];
+
+ if (source != null)
+ {
+ portConstraint[0] = mxUtils.getPortConstraints(source, state, true,
+ mxConstants.DIRECTION_MASK_ALL);
+ }
+
+ if (target != null)
+ {
+ portConstraint[1] = mxUtils.getPortConstraints(target, state, false,
+ mxConstants.DIRECTION_MASK_ALL);
+ }
+
+ var dir = [0, 0] ;
+
+ // Work out which faces of the vertices present against each other
+ // in a way that would allow a 3-segment connection if port constraints
+ // permitted.
+ // geo -> [source, target] [x, y, width, height]
+ var geo = [ [sourceX, sourceY, sourceWidth, sourceHeight] ,
+ [targetX, targetY, targetWidth, targetHeight] ];
+
+ for (var i = 0; i < 2; i++)
+ {
+ mxEdgeStyle.limits[i][1] = geo[i][0] - scaledOrthBuffer;
+ mxEdgeStyle.limits[i][2] = geo[i][1] - scaledOrthBuffer;
+ mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + scaledOrthBuffer;
+ mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + scaledOrthBuffer;
+ }
+
+ // Work out which quad the target is in
+ var sourceCenX = geo[0][0] + geo[0][2] / 2.0;
+ var sourceCenY = geo[0][1] + geo[0][3] / 2.0;
+ var targetCenX = geo[1][0] + geo[1][2] / 2.0;
+ var targetCenY = geo[1][1] + geo[1][3] / 2.0;
+
+ var dx = sourceCenX - targetCenX;
+ var dy = sourceCenY - targetCenY;
+
+ var quad = 0;
+
+ if (dx < 0)
+ {
+ if (dy < 0)
+ {
+ quad = 2;
+ }
+ else
+ {
+ quad = 1;
+ }
+ }
+ else
+ {
+ if (dy <= 0)
+ {
+ quad = 3;
+
+ // Special case on x = 0 and negative y
+ if (dx == 0)
+ {
+ quad = 2;
+ }
+ }
+ }
+
+ // Check for connection constraints
+ var currentTerm = null;
+
+ if (source != null)
+ {
+ currentTerm = p0;
+ }
+
+ var constraint = [ [0.5, 0.5] , [0.5, 0.5] ];
+
+ for (var i = 0; i < 2; i++)
+ {
+ if (currentTerm != null)
+ {
+ constraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2];
+
+ if (constraint[i][0] < 0.01)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_WEST;
+ }
+ else if (constraint[i][0] > 0.99)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_EAST;
+ }
+
+ constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3];
+
+ if (constraint[i][1] < 0.01)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_NORTH;
+ }
+ else if (constraint[i][1] > 0.99)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_SOUTH;
+ }
+ }
+
+ currentTerm = null;
+
+ if (target != null)
+ {
+ currentTerm = pe;
+ }
+ }
+
+ var sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);
+ var sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);
+ var sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);
+ var sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);
+
+ mxEdgeStyle.vertexSeperations[1] = Math.max(
+ sourceLeftDist - 2 * scaledOrthBuffer, 0);
+ mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - 2 * scaledOrthBuffer,
+ 0);
+ mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - 2
+ * scaledOrthBuffer, 0);
+ mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - 2
+ * scaledOrthBuffer, 0);
+
+ //==============================================================
+ // Start of source and target direction determination
+
+ // Work through the preferred orientations by relative positioning
+ // of the vertices and list them in preferred and available order
+
+ var dirPref = [];
+ var horPref = [];
+ var vertPref = [];
+
+ horPref[0] = (sourceLeftDist >= sourceRightDist) ? mxConstants.DIRECTION_MASK_WEST
+ : mxConstants.DIRECTION_MASK_EAST;
+ vertPref[0] = (sourceTopDist >= sourceBottomDist) ? mxConstants.DIRECTION_MASK_NORTH
+ : mxConstants.DIRECTION_MASK_SOUTH;
+
+ horPref[1] = mxUtils.reversePortConstraints(horPref[0]);
+ vertPref[1] = mxUtils.reversePortConstraints(vertPref[0]);
+
+ var preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist
+ : sourceRightDist;
+ var preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist
+ : sourceBottomDist;
+
+ var prefOrdering = [ [0, 0] , [0, 0] ];
+ var preferredOrderSet = false;
+
+ // If the preferred port isn't available, switch it
+ for (var i = 0; i < 2; i++)
+ {
+ if (dir[i] != 0x0)
+ {
+ continue;
+ }
+
+ if ((horPref[i] & portConstraint[i]) == 0)
+ {
+ horPref[i] = mxUtils.reversePortConstraints(horPref[i]);
+ }
+
+ if ((vertPref[i] & portConstraint[i]) == 0)
+ {
+ vertPref[i] = mxUtils
+ .reversePortConstraints(vertPref[i]);
+ }
+
+ prefOrdering[i][0] = vertPref[i];
+ prefOrdering[i][1] = horPref[i];
+ }
+
+ if (preferredVertDist > scaledOrthBuffer * 2
+ && preferredHorizDist > scaledOrthBuffer * 2)
+ {
+ // Possibility of two segment edge connection
+ if (((horPref[0] & portConstraint[0]) > 0)
+ && ((vertPref[1] & portConstraint[1]) > 0))
+ {
+ prefOrdering[0][0] = horPref[0];
+ prefOrdering[0][1] = vertPref[0];
+ prefOrdering[1][0] = vertPref[1];
+ prefOrdering[1][1] = horPref[1];
+ preferredOrderSet = true;
+ }
+ else if (((vertPref[0] & portConstraint[0]) > 0)
+ && ((horPref[1] & portConstraint[1]) > 0))
+ {
+ prefOrdering[0][0] = vertPref[0];
+ prefOrdering[0][1] = horPref[0];
+ prefOrdering[1][0] = horPref[1];
+ prefOrdering[1][1] = vertPref[1];
+ preferredOrderSet = true;
+ }
+ }
+ if (preferredVertDist > scaledOrthBuffer * 2 && !preferredOrderSet)
+ {
+ prefOrdering[0][0] = vertPref[0];
+ prefOrdering[0][1] = horPref[0];
+ prefOrdering[1][0] = vertPref[1];
+ prefOrdering[1][1] = horPref[1];
+ preferredOrderSet = true;
+
+ }
+ if (preferredHorizDist > scaledOrthBuffer * 2 && !preferredOrderSet)
+ {
+ prefOrdering[0][0] = horPref[0];
+ prefOrdering[0][1] = vertPref[0];
+ prefOrdering[1][0] = horPref[1];
+ prefOrdering[1][1] = vertPref[1];
+ preferredOrderSet = true;
+ }
+
+ // The source and target prefs are now an ordered list of
+ // the preferred port selections
+ // It the list can contain gaps, compact it
+
+ for (var i = 0; i < 2; i++)
+ {
+ if (dir[i] != 0x0)
+ {
+ continue;
+ }
+
+ if ((prefOrdering[i][0] & portConstraint[i]) == 0)
+ {
+ prefOrdering[i][0] = prefOrdering[i][1];
+ }
+
+ dirPref[i] = prefOrdering[i][0] & portConstraint[i];
+ dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;
+ dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;
+ dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;
+
+ if ((dirPref[i] & 0xF) == 0)
+ {
+ dirPref[i] = dirPref[i] << 8;
+ }
+ if ((dirPref[i] & 0xF00) == 0)
+ {
+ dirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8;
+ }
+ if ((dirPref[i] & 0xF0000) == 0)
+ {
+ dirPref[i] = (dirPref[i] & 0xFFFF)
+ | ((dirPref[i] & 0xF000000) >> 8);
+ }
+
+ dir[i] = dirPref[i] & 0xF;
+
+ if (portConstraint[i] == mxConstants.DIRECTION_MASK_WEST
+ || portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH
+ || portConstraint[i] == mxConstants.DIRECTION_MASK_EAST
+ || portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH)
+ {
+ dir[i] = portConstraint[i];
+ }
+ }
+
+ //==============================================================
+ // End of source and target direction determination
+
+ var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[0];
+ var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[1];
+
+ sourceIndex -= quad;
+ targetIndex -= quad;
+
+ if (sourceIndex < 1)
+ {
+ sourceIndex += 4;
+ }
+ if (targetIndex < 1)
+ {
+ targetIndex += 4;
+ }
+
+ var routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1];
+
+ mxEdgeStyle.wayPoints1[0][0] = geo[0][0];
+ mxEdgeStyle.wayPoints1[0][1] = geo[0][1];
+
+ switch (dir[0])
+ {
+ case mxConstants.DIRECTION_MASK_WEST:
+ mxEdgeStyle.wayPoints1[0][0] -= scaledOrthBuffer;
+ mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+ break;
+ case mxConstants.DIRECTION_MASK_SOUTH:
+ mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+ mxEdgeStyle.wayPoints1[0][1] += geo[0][3] + scaledOrthBuffer;
+ break;
+ case mxConstants.DIRECTION_MASK_EAST:
+ mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + scaledOrthBuffer;
+ mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+ break;
+ case mxConstants.DIRECTION_MASK_NORTH:
+ mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+ mxEdgeStyle.wayPoints1[0][1] -= scaledOrthBuffer;
+ break;
+ }
+
+ var currentIndex = 0;
+
+ // Orientation, 0 horizontal, 1 vertical
+ var lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+ : 1;
+ var initialOrientation = lastOrientation;
+ var currentOrientation = 0;
+
+ for (var i = 0; i < routePattern.length; i++)
+ {
+ var nextDirection = routePattern[i] & 0xF;
+
+ // Rotate the index of this direction by the quad
+ // to get the real direction
+ var directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3
+ : nextDirection;
+
+ directionIndex += quad;
+
+ if (directionIndex > 4)
+ {
+ directionIndex -= 4;
+ }
+
+ var direction = mxEdgeStyle.dirVectors[directionIndex - 1];
+
+ currentOrientation = (directionIndex % 2 > 0) ? 0 : 1;
+ // Only update the current index if the point moved
+ // in the direction of the current segment move,
+ // otherwise the same point is moved until there is
+ // a segment direction change
+ if (currentOrientation != lastOrientation)
+ {
+ currentIndex++;
+ // Copy the previous way point into the new one
+ // We can't base the new position on index - 1
+ // because sometime elbows turn out not to exist,
+ // then we'd have to rewind.
+ mxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0];
+ mxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1];
+ }
+
+ var tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0;
+ var sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0;
+ var side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5;
+ side = side << quad;
+
+ if (side > 0xF)
+ {
+ side = side >> 4;
+ }
+
+ var center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0;
+
+ if ((sou || tar) && side < 9)
+ {
+ var limit = 0;
+ var souTar = sou ? 0 : 1;
+
+ if (center && currentOrientation == 0)
+ {
+ limit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2];
+ }
+ else if (center)
+ {
+ limit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3];
+ }
+ else
+ {
+ limit = mxEdgeStyle.limits[souTar][side];
+ }
+
+ if (currentOrientation == 0)
+ {
+ var lastX = mxEdgeStyle.wayPoints1[currentIndex][0];
+ var deltaX = (limit - lastX) * direction[0];
+
+ if (deltaX > 0)
+ {
+ mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+ * deltaX;
+ }
+ }
+ else
+ {
+ var lastY = mxEdgeStyle.wayPoints1[currentIndex][1];
+ var deltaY = (limit - lastY) * direction[1];
+
+ if (deltaY > 0)
+ {
+ mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+ * deltaY;
+ }
+ }
+ }
+
+ else if (center)
+ {
+ // Which center we're travelling to depend on the current direction
+ mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+ * Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+ mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+ * Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+ }
+
+ if (currentIndex > 0
+ && mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] == mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation])
+ {
+ currentIndex--;
+ }
+ else
+ {
+ lastOrientation = currentOrientation;
+ }
+ }
+
+ for (var i = 0; i <= currentIndex; i++)
+ {
+ if (i == currentIndex)
+ {
+ // Last point can cause last segment to be in
+ // same direction as jetty/approach. If so,
+ // check the number of points is consistent
+ // with the relative orientation of source and target
+ // jettys. Same orientation requires an even
+ // number of turns (points), different requires
+ // odd.
+ var targetOrientation = (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+ : 1;
+ var sameOrient = targetOrientation == initialOrientation ? 0 : 1;
+
+ // (currentIndex + 1) % 2 is 0 for even number of points,
+ // 1 for odd
+ if (sameOrient != (currentIndex + 1) % 2)
+ {
+ // The last point isn't required
+ break;
+ }
+ }
+
+ result.push(new mxPoint(mxEdgeStyle.wayPoints1[i][0], mxEdgeStyle.wayPoints1[i][1]));
+ }
+ },
+
+ getRoutePattern: function(dir, quad, dx, dy)
+ {
+ var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[0];
+ var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[1];
+
+ sourceIndex -= quad;
+ targetIndex -= quad;
+
+ if (sourceIndex < 1)
+ {
+ sourceIndex += 4;
+ }
+ if (targetIndex < 1)
+ {
+ targetIndex += 4;
+ }
+
+ var result = routePatterns[sourceIndex - 1][targetIndex - 1];
+
+ if (dx == 0 || dy == 0)
+ {
+ if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null)
+ {
+ result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];
+ }
+ }
+
+ return result;
+ }
+}; \ No newline at end of file
diff --git a/src/js/view/mxGraph.js b/src/js/view/mxGraph.js
new file mode 100644
index 0000000..7c90f9b
--- /dev/null
+++ b/src/js/view/mxGraph.js
@@ -0,0 +1,11176 @@
+/**
+ * $Id: mxGraph.js,v 1.702 2012-12-13 15:07:34 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraph
+ *
+ * Extends <mxEventSource> to implement a graph component for
+ * the browser. This is the main class of the package. To activate
+ * panning and connections use <setPanning> and <setConnectable>.
+ * For rubberband selection you must create a new instance of
+ * <mxRubberband>. The following listeners are added to
+ * <mouseListeners> by default:
+ *
+ * - <tooltipHandler>: <mxTooltipHandler> that displays tooltips
+ * - <panningHandler>: <mxPanningHandler> for panning and popup menus
+ * - <connectionHandler>: <mxConnectionHandler> for creating connections
+ * - <graphHandler>: <mxGraphHandler> for moving and cloning cells
+ *
+ * These listeners will be called in the above order if they are enabled.
+ *
+ * Background Images:
+ *
+ * To display a background image, set the image, image width and
+ * image height using <setBackgroundImage>. If one of the
+ * above values has changed then the <view>'s <mxGraphView.validate>
+ * should be invoked.
+ *
+ * Cell Images:
+ *
+ * To use images in cells, a shape must be specified in the default
+ * vertex style (or any named style). Possible shapes are
+ * <mxConstants.SHAPE_IMAGE> and <mxConstants.SHAPE_LABEL>.
+ * The code to change the shape used in the default vertex style,
+ * the following code is used:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;
+ * (end)
+ *
+ * For the default vertex style, the image to be displayed can be
+ * specified in a cell's style using the <mxConstants.STYLE_IMAGE>
+ * key and the image URL as a value, for example:
+ *
+ * (code)
+ * image=http://www.example.com/image.gif
+ * (end)
+ *
+ * For a named style, the the stylename must be the first element
+ * of the cell style:
+ *
+ * (code)
+ * stylename;image=http://www.example.com/image.gif
+ * (end)
+ *
+ * A cell style can have any number of key=value pairs added, divided
+ * by a semicolon as follows:
+ *
+ * (code)
+ * [stylename;|key=value;]
+ * (end)
+ *
+ * Labels:
+ *
+ * The cell labels are defined by <getLabel> which uses <convertValueToString>
+ * if <labelsVisible> is true. If a label must be rendered as HTML markup, then
+ * <isHtmlLabel> should return true for the respective cell. If all labels
+ * contain HTML markup, <htmlLabels> can be set to true. NOTE: Enabling HTML
+ * labels carries a possible security risk (see the section on security in
+ * the manual).
+ *
+ * If wrapping is needed for a label, then <isHtmlLabel> and <isWrapping> must
+ * return true for the cell whose label should be wrapped. See <isWrapping> for
+ * an example.
+ *
+ * If clipping is needed to keep the rendering of a HTML label inside the
+ * bounds of its vertex, then <isClipping> should return true for the
+ * respective cell.
+ *
+ * By default, edge labels are movable and vertex labels are fixed. This can be
+ * changed by setting <edgeLabelsMovable> and <vertexLabelsMovable>, or by
+ * overriding <isLabelMovable>.
+ *
+ * In-place Editing:
+ *
+ * In-place editing is started with a doubleclick or by typing F2.
+ * Programmatically, <edit> is used to check if the cell is editable
+ * (<isCellEditable>) and call <startEditingAtCell>, which invokes
+ * <mxCellEditor.startEditing>. The editor uses the value returned
+ * by <getEditingValue> as the editing value.
+ *
+ * After in-place editing, <labelChanged> is called, which invokes
+ * <mxGraphModel.setValue>, which in turn calls
+ * <mxGraphModel.valueForCellChanged> via <mxValueChange>.
+ *
+ * The event that triggers in-place editing is passed through to the
+ * <cellEditor>, which may take special actions depending on the type of the
+ * event or mouse location, and is also passed to <getEditingValue>. The event
+ * is then passed back to the event processing functions which can perform
+ * specific actions based on the trigger event.
+ *
+ * Tooltips:
+ *
+ * Tooltips are implemented by <getTooltip>, which calls <getTooltipForCell>
+ * if a cell is under the mousepointer. The default implementation checks if
+ * the cell has a getTooltip function and calls it if it exists. Hence, in order
+ * to provide custom tooltips, the cell must provide a getTooltip function, or
+ * one of the two above functions must be overridden.
+ *
+ * Typically, for custom cell tooltips, the latter function is overridden as
+ * follows:
+ *
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ * var label = this.convertValueToString(cell);
+ * return 'Tooltip for '+label;
+ * }
+ * (end)
+ *
+ * When using a config file, the function is overridden in the mxGraph section
+ * using the following entry:
+ *
+ * (code)
+ * <add as="getTooltipForCell"><![CDATA[
+ * function(cell)
+ * {
+ * var label = this.convertValueToString(cell);
+ * return 'Tooltip for '+label;
+ * }
+ * ]]></add>
+ * (end)
+ *
+ * "this" refers to the graph in the implementation, so for example to check if
+ * a cell is an edge, you use this.getModel().isEdge(cell)
+ *
+ * For replacing the default implementation of <getTooltipForCell> (rather than
+ * replacing the function on a specific instance), the following code should be
+ * used after loading the JavaScript files, but before creating a new mxGraph
+ * instance using <mxGraph>:
+ *
+ * (code)
+ * mxGraph.prototype.getTooltipForCell = function(cell)
+ * {
+ * var label = this.convertValueToString(cell);
+ * return 'Tooltip for '+label;
+ * }
+ * (end)
+ *
+ * Shapes & Styles:
+ *
+ * The implementation of new shapes is demonstrated in the examples. We'll assume
+ * that we have implemented a custom shape with the name BoxShape which we want
+ * to use for drawing vertices. To use this shape, it must first be registered in
+ * the cell renderer as follows:
+ *
+ * (code)
+ * graph.cellRenderer.registerShape('box', BoxShape);
+ * (end)
+ *
+ * The code registers the BoxShape constructor under the name box in the cell
+ * renderer of the graph. The shape can now be referenced using the shape-key in
+ * a style definition. (The cell renderer contains a set of additional shapes,
+ * namely one for each constant with a SHAPE-prefix in <mxConstants>.)
+ *
+ * Styles are a collection of key, value pairs and a stylesheet is a collection
+ * of named styles. The names are referenced by the cellstyle, which is stored
+ * in <mxCell.style> with the following format: [stylename;|key=value;]. The
+ * string is resolved to a collection of key, value pairs, where the keys are
+ * overridden with the values in the string.
+ *
+ * When introducing a new shape, the name under which the shape is registered
+ * must be used in the stylesheet. There are three ways of doing this:
+ *
+ * - By changing the default style, so that all vertices will use the new
+ * shape
+ * - By defining a new style, so that only vertices with the respective
+ * cellstyle will use the new shape
+ * - By using shape=box in the cellstyle's optional list of key, value pairs
+ * to be overridden
+ *
+ * In the first case, the code to fetch and modify the default style for
+ * vertices is as follows:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * (end)
+ *
+ * The code takes the default vertex style, which is used for all vertices that
+ * do not have a specific cellstyle, and modifies the value for the shape-key
+ * in-place to use the new BoxShape for drawing vertices. This is done by
+ * assigning the box value in the second line, which refers to the name of the
+ * BoxShape in the cell renderer.
+ *
+ * In the second case, a collection of key, value pairs is created and then
+ * added to the stylesheet under a new name. In order to distinguish the
+ * shapename and the stylename we'll use boxstyle for the stylename:
+ *
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * style[mxConstants.STYLE_STROKECOLOR] = '#000000';
+ * style[mxConstants.STYLE_FONTCOLOR] = '#000000';
+ * graph.getStylesheet().putCellStyle('boxstyle', style);
+ * (end)
+ *
+ * The code adds a new style with the name boxstyle to the stylesheet. To use
+ * this style with a cell, it must be referenced from the cellstyle as follows:
+ *
+ * (code)
+ * var vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20,
+ * 'boxstyle');
+ * (end)
+ *
+ * To summarize, each new shape must be registered in the <mxCellRenderer> with
+ * a unique name. That name is then used as the value of the shape-key in a
+ * default or custom style. If there are multiple custom shapes, then there
+ * should be a separate style for each shape.
+ *
+ * Inheriting Styles:
+ *
+ * For fill-, stroke-, gradient- and indicatorColors special keywords can be
+ * used. The inherit keyword for one of these colors will inherit the color
+ * for the same key from the parent cell. The swimlane keyword does the same,
+ * but inherits from the nearest swimlane in the ancestor hierarchy. Finally,
+ * the indicated keyword will use the color of the indicator as the color for
+ * the given key.
+ *
+ * Scrollbars:
+ *
+ * The <containers> overflow CSS property defines if scrollbars are used to
+ * display the graph. For values of 'auto' or 'scroll', the scrollbars will
+ * be shown. Note that the <resizeContainer> flag is normally not used
+ * together with scrollbars, as it will resize the container to match the
+ * size of the graph after each change.
+ *
+ * Multiplicities and Validation:
+ *
+ * To control the possible connections in mxGraph, <getEdgeValidationError> is
+ * used. The default implementation of the function uses <multiplicities>,
+ * which is an array of <mxMultiplicity>. Using this class allows to establish
+ * simple multiplicities, which are enforced by the graph.
+ *
+ * The <mxMultiplicity> uses <mxCell.is> to determine for which terminals it
+ * applies. The default implementation of <mxCell.is> works with DOM nodes (XML
+ * nodes) and checks if the given type parameter matches the nodeName of the
+ * node (case insensitive). Optionally, an attributename and value can be
+ * specified which are also checked.
+ *
+ * <getEdgeValidationError> is called whenever the connectivity of an edge
+ * changes. It returns an empty string or an error message if the edge is
+ * invalid or null if the edge is valid. If the returned string is not empty
+ * then it is displayed as an error message.
+ *
+ * <mxMultiplicity> allows to specify the multiplicity between a terminal and
+ * its possible neighbors. For example, if any rectangle may only be connected
+ * to, say, a maximum of two circles you can add the following rule to
+ * <multiplicities>:
+ *
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ * true, 'rectangle', null, null, 0, 2, ['circle'],
+ * 'Only 2 targets allowed',
+ * 'Only shape targets allowed'));
+ * (end)
+ *
+ * This will display the first error message whenever a rectangle is connected
+ * to more than two circles and the second error message if a rectangle is
+ * connected to anything but a circle.
+ *
+ * For certain multiplicities, such as a minimum of 1 connection, which cannot
+ * be enforced at cell creation time (unless the cell is created together with
+ * the connection), mxGraph offers <validate> which checks all multiplicities
+ * for all cells and displays the respective error messages in an overlay icon
+ * on the cells.
+ *
+ * If a cell is collapsed and contains validation errors, a respective warning
+ * icon is attached to the collapsed cell.
+ *
+ * Auto-Layout:
+ *
+ * For automatic layout, the <getLayout> hook is provided in <mxLayoutManager>.
+ * It can be overridden to return a layout algorithm for the children of a
+ * given cell.
+ *
+ * Unconnected edges:
+ *
+ * The default values for all switches are designed to meet the requirements of
+ * general diagram drawing applications. A very typical set of settings to
+ * avoid edges that are not connected is the following:
+ *
+ * (code)
+ * graph.setAllowDanglingEdges(false);
+ * graph.setDisconnectOnMove(false);
+ * (end)
+ *
+ * Setting the <cloneInvalidEdges> switch to true is optional. This switch
+ * controls if edges are inserted after a copy, paste or clone-drag if they are
+ * invalid. For example, edges are invalid if copied or control-dragged without
+ * having selected the corresponding terminals and allowDanglingEdges is
+ * false, in which case the edges will not be cloned if the switch is false.
+ *
+ * Output:
+ *
+ * To produce an XML representation for a diagram, the following code can be
+ * used.
+ *
+ * (code)
+ * var enc = new mxCodec(mxUtils.createXmlDocument());
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ *
+ * This will produce an XML node than can be handled using the DOM API or
+ * turned into a string representation using the following code:
+ *
+ * (code)
+ * var xml = mxUtils.getXml(node);
+ * (end)
+ *
+ * To obtain a formatted string, mxUtils.getPrettyXml can be used instead.
+ *
+ * This string can now be stored in a local persistent storage (for example
+ * using Google Gears) or it can be passed to a backend using mxUtils.post as
+ * follows. The url variable is the URL of the Java servlet, PHP page or HTTP
+ * handler, depending on the server.
+ *
+ * (code)
+ * var xmlString = encodeURIComponent(mxUtils.getXml(node));
+ * mxUtils.post(url, 'xml='+xmlString, function(req)
+ * {
+ * // Process server response using req of type mxXmlRequest
+ * });
+ * (end)
+ *
+ * Input:
+ *
+ * To load an XML representation of a diagram into an existing graph object
+ * mxUtils.load can be used as follows. The url variable is the URL of the Java
+ * servlet, PHP page or HTTP handler that produces the XML string.
+ *
+ * (code)
+ * var xmlDoc = mxUtils.load(url).getXml();
+ * var node = xmlDoc.documentElement;
+ * var dec = new mxCodec(node.ownerDocument);
+ * dec.decode(node, graph.getModel());
+ * (end)
+ *
+ * For creating a page that loads the client and a diagram using a single
+ * request please refer to the deployment examples in the backends.
+ *
+ * Functional dependencies:
+ *
+ * (see images/callgraph.png)
+ *
+ * Resources:
+ *
+ * resources/graph - Language resources for mxGraph
+ *
+ * Group: Events
+ *
+ * Event: mxEvent.ROOT
+ *
+ * Fires if the root in the model has changed. This event has no properties.
+ *
+ * Event: mxEvent.ALIGN_CELLS
+ *
+ * Fires between begin- and endUpdate in <alignCells>. The <code>cells</code>
+ * and <code>align</code> properties contain the respective arguments that were
+ * passed to <alignCells>.
+ *
+ * Event: mxEvent.FLIP_EDGE
+ *
+ * Fires between begin- and endUpdate in <flipEdge>. The <code>edge</code>
+ * property contains the edge passed to <flipEdge>.
+ *
+ * Event: mxEvent.ORDER_CELLS
+ *
+ * Fires between begin- and endUpdate in <orderCells>. The <code>cells</code>
+ * and <code>back</code> properties contain the respective arguments that were
+ * passed to <orderCells>.
+ *
+ * Event: mxEvent.CELLS_ORDERED
+ *
+ * Fires between begin- and endUpdate in <cellsOrdered>. The <code>cells</code>
+ * and <code>back</code> arguments contain the respective arguments that were
+ * passed to <cellsOrdered>.
+ *
+ * Event: mxEvent.GROUP_CELLS
+ *
+ * Fires between begin- and endUpdate in <groupCells>. The <code>group</code>,
+ * <code>cells</code> and <code>border</code> arguments contain the respective
+ * arguments that were passed to <groupCells>.
+ *
+ * Event: mxEvent.UNGROUP_CELLS
+ *
+ * Fires between begin- and endUpdate in <ungroupCells>. The <code>cells</code>
+ * property contains the array of cells that was passed to <ungroupCells>.
+ *
+ * Event: mxEvent.REMOVE_CELLS_FROM_PARENT
+ *
+ * Fires between begin- and endUpdate in <removeCellsFromParent>. The
+ * <code>cells</code> property contains the array of cells that was passed to
+ * <removeCellsFromParent>.
+ *
+ * Event: mxEvent.ADD_CELLS
+ *
+ * Fires between begin- and endUpdate in <addCells>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code> and
+ * <code>target</code> properties contain the respective arguments that were
+ * passed to <addCells>.
+ *
+ * Event: mxEvent.CELLS_ADDED
+ *
+ * Fires between begin- and endUpdate in <cellsAdded>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code>,
+ * <code>target</code> and <code>absolute</code> properties contain the
+ * respective arguments that were passed to <cellsAdded>.
+ *
+ * Event: mxEvent.REMOVE_CELLS
+ *
+ * Fires between begin- and endUpdate in <removeCells>. The <code>cells</code>
+ * and <code>includeEdges</code> arguments contain the respective arguments
+ * that were passed to <removeCells>.
+ *
+ * Event: mxEvent.CELLS_REMOVED
+ *
+ * Fires between begin- and endUpdate in <cellsRemoved>. The <code>cells</code>
+ * argument contains the array of cells that was removed.
+ *
+ * Event: mxEvent.SPLIT_EDGE
+ *
+ * Fires between begin- and endUpdate in <splitEdge>. The <code>edge</code>
+ * property contains the edge to be splitted, the <code>cells</code>,
+ * <code>newEdge</code>, <code>dx</code> and <code>dy</code> properties contain
+ * the respective arguments that were passed to <splitEdge>.
+ *
+ * Event: mxEvent.TOGGLE_CELLS
+ *
+ * Fires between begin- and endUpdate in <toggleCells>. The <code>show</code>,
+ * <code>cells</code> and <code>includeEdges</code> properties contain the
+ * respective arguments that were passed to <toggleCells>.
+ *
+ * Event: mxEvent.FOLD_CELLS
+ *
+ * Fires between begin- and endUpdate in <foldCells>. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to <foldCells>.
+ *
+ * Event: mxEvent.CELLS_FOLDED
+ *
+ * Fires between begin- and endUpdate in cellsFolded. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to
+ * <cellsFolded>.
+ *
+ * Event: mxEvent.UPDATE_CELL_SIZE
+ *
+ * Fires between begin- and endUpdate in <updateCellSize>. The
+ * <code>cell</code> and <code>ignoreChildren</code> properties contain the
+ * respective arguments that were passed to <updateCellSize>.
+ *
+ * Event: mxEvent.RESIZE_CELLS
+ *
+ * Fires between begin- and endUpdate in <resizeCells>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <resizeCells>.
+ *
+ * Event: mxEvent.CELLS_RESIZED
+ *
+ * Fires between begin- and endUpdate in <cellsResized>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <cellsResized>.
+ *
+ * Event: mxEvent.MOVE_CELLS
+ *
+ * Fires between begin- and endUpdate in <moveCells>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code>, <code>clone</code>, <code>target</code>
+ * and <code>event</code> properties contain the respective arguments that
+ * were passed to <moveCells>.
+ *
+ * Event: mxEvent.CELLS_MOVED
+ *
+ * Fires between begin- and endUpdate in <cellsMoved>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code> and <code>disconnect</code> properties
+ * contain the respective arguments that were passed to <cellsMoved>.
+ *
+ * Event: mxEvent.CONNECT_CELL
+ *
+ * Fires between begin- and endUpdate in <connectCell>. The <code>edge</code>,
+ * <code>terminal</code> and <code>source</code> properties contain the
+ * respective arguments that were passed to <connectCell>.
+ *
+ * Event: mxEvent.CELL_CONNECTED
+ *
+ * Fires between begin- and endUpdate in <cellConnected>. The
+ * <code>edge</code>, <code>terminal</code> and <code>source</code> properties
+ * contain the respective arguments that were passed to <cellConnected>.
+ *
+ * Event: mxEvent.REFRESH
+ *
+ * Fires after <refresh> was executed. This event has no properties.
+ *
+ * Event: mxEvent.CLICK
+ *
+ * Fires in <click> after a click event. The <code>event</code> property
+ * contains the original mouse event and <code>cell</code> property contains
+ * the cell under the mouse or null if the background was clicked.
+ *
+ * To handle a click event, use the following code:
+ *
+ * (code)
+ * graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ * var e = evt.getProperty('event'); // mouse event
+ * var cell = evt.getProperty('cell'); // cell may be null
+ *
+ * if (!evt.isConsumed())
+ * {
+ * if (cell != null)
+ * {
+ * // Do something useful with cell and consume the event
+ * evt.consume();
+ * }
+ * }
+ * });
+ * (end)
+ *
+ * Event: mxEvent.DOUBLE_CLICK
+ *
+ * Fires in <dblClick> after a double click. The <code>event</code> property
+ * contains the original mouse event and the <code>cell</code> property
+ * contains the cell under the mouse or null if the background was clicked.
+ *
+ * Event: mxEvent.SIZE
+ *
+ * Fires after <sizeDidChange> was executed. The <code>bounds</code> property
+ * contains the new graph bounds.
+ *
+ * Event: mxEvent.START_EDITING
+ *
+ * Fires before the in-place editor starts in <startEditingAtCell>. The
+ * <code>cell</code> property contains the cell that is being edited and the
+ * <code>event</code> property contains the optional event argument that was
+ * passed to <startEditingAtCell>.
+ *
+ * Event: mxEvent.LABEL_CHANGED
+ *
+ * Fires between begin- and endUpdate in <cellLabelChanged>. The
+ * <code>cell</code> property contains the cell, the <code>value</code>
+ * property contains the new value for the cell and the optional
+ * <code>event</code> property contains the mouse event that started the edit.
+ *
+ * Event: mxEvent.ADD_OVERLAY
+ *
+ * Fires after an overlay is added in <addCellOverlay>. The <code>cell</code>
+ * property contains the cell and the <code>overlay</code> property contains
+ * the <mxCellOverlay> that was added.
+ *
+ * Event: mxEvent.REMOVE_OVERLAY
+ *
+ * Fires after an overlay is removed in <removeCellOverlay> and
+ * <removeCellOverlays>. The <code>cell</code> property contains the cell and
+ * the <code>overlay</code> property contains the <mxCellOverlay> that was
+ * removed.
+ *
+ * Constructor: mxGraph
+ *
+ * Constructs a new mxGraph in the specified container. Model is an optional
+ * mxGraphModel. If no model is provided, a new mxGraphModel instance is
+ * used as the model. The container must have a valid owner document prior
+ * to calling this function in Internet Explorer. RenderHint is a string to
+ * affect the display performance and rendering in IE, but not in SVG-based
+ * browsers. The parameter is mapped to <dialect>, which may
+ * be one of <mxConstants.DIALECT_SVG> for SVG-based browsers,
+ * <mxConstants.DIALECT_STRICTHTML> for fastest display mode,
+ * <mxConstants.DIALECT_PREFERHTML> for faster display mode,
+ * <mxConstants.DIALECT_MIXEDHTML> for fast and <mxConstants.DIALECT_VML>
+ * for exact display mode (slowest). The dialects are defined in mxConstants.
+ * The default values are DIALECT_SVG for SVG-based browsers and
+ * DIALECT_MIXED for IE.
+ *
+ * The possible values for the renderingHint parameter are explained below:
+ *
+ * fast - The parameter is based on the fact that the display performance is
+ * highly improved in IE if the VML is not contained within a VML group
+ * element. The lack of a group element only slightly affects the display while
+ * panning, but improves the performance by almost a factor of 2, while keeping
+ * the display sufficiently accurate. This also allows to render certain shapes as HTML
+ * if the display accuracy is not affected, which is implemented by
+ * <mxShape.isMixedModeHtml>. This is the default setting and is mapped to
+ * DIALECT_MIXEDHTML.
+ * faster - Same as fast, but more expensive shapes are avoided. This is
+ * controlled by <mxShape.preferModeHtml>. The default implementation will
+ * avoid gradients and rounded rectangles, but more significant shapes, such
+ * as rhombus, ellipse, actor and cylinder will be rendered accurately. This
+ * setting is mapped to DIALECT_PREFERHTML.
+ * fastest - Almost anything will be rendered in Html. This allows for
+ * rectangles, labels and images. This setting is mapped to
+ * DIALECT_STRICTHTML.
+ * exact - If accurate panning is required and if the diagram is small (up
+ * to 100 cells), then this value should be used. In this mode, a group is
+ * created that contains the VML. This allows for accurate panning and is
+ * mapped to DIALECT_VML.
+ *
+ * Example:
+ *
+ * To create a graph inside a DOM node with an id of graph:
+ * (code)
+ * var container = document.getElementById('graph');
+ * var graph = new mxGraph(container);
+ * (end)
+ *
+ * Parameters:
+ *
+ * container - Optional DOM node that acts as a container for the graph.
+ * If this is null then the container can be initialized later using
+ * <init>.
+ * model - Optional <mxGraphModel> that constitutes the graph data.
+ * renderHint - Optional string that specifies the display accuracy and
+ * performance. Default is mxConstants.DIALECT_MIXEDHTML (for IE).
+ * stylesheet - Optional <mxStylesheet> to be used in the graph.
+ */
+function mxGraph(container, model, renderHint, stylesheet)
+{
+ // Initializes the variable in case the prototype has been
+ // modified to hold some listeners (which is possible because
+ // the createHandlers call is executed regardless of the
+ // arguments passed into the ctor).
+ this.mouseListeners = null;
+
+ // Converts the renderHint into a dialect
+ this.renderHint = renderHint;
+
+ if (mxClient.IS_SVG)
+ {
+ this.dialect = mxConstants.DIALECT_SVG;
+ }
+ else if (renderHint == mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML)
+ {
+ this.dialect = mxConstants.DIALECT_VML;
+ }
+ else if (renderHint == mxConstants.RENDERING_HINT_FASTEST)
+ {
+ this.dialect = mxConstants.DIALECT_STRICTHTML;
+ }
+ else if (renderHint == mxConstants.RENDERING_HINT_FASTER)
+ {
+ this.dialect = mxConstants.DIALECT_PREFERHTML;
+ }
+ else // default for VML
+ {
+ this.dialect = mxConstants.DIALECT_MIXEDHTML;
+ }
+
+ // Initializes the main members that do not require a container
+ this.model = (model != null) ? model : new mxGraphModel();
+ this.multiplicities = [];
+ this.imageBundles = [];
+ this.cellRenderer = this.createCellRenderer();
+ this.setSelectionModel(this.createSelectionModel());
+ this.setStylesheet((stylesheet != null) ? stylesheet : this.createStylesheet());
+ this.view = this.createGraphView();
+
+ // Adds a graph model listener to update the view
+ this.graphModelChangeListener = mxUtils.bind(this, function(sender, evt)
+ {
+ this.graphModelChanged(evt.getProperty('edit').changes);
+ });
+
+ this.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener);
+
+ // Installs basic event handlers with disabled default settings.
+ this.createHandlers();
+
+ // Initializes the display if a container was specified
+ if (container != null)
+ {
+ this.init(container);
+ }
+
+ this.view.revalidate();
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+ mxResources.add(mxClient.basePath+'/resources/graph');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraph.prototype = new mxEventSource();
+mxGraph.prototype.constructor = mxGraph;
+
+/**
+ * Variable: EMPTY_ARRAY
+ *
+ * Immutable empty array instance.
+ */
+mxGraph.prototype.EMPTY_ARRAY = [];
+
+/**
+ * Group: Variables
+ */
+
+/**
+ * Variable: mouseListeners
+ *
+ * Holds the mouse event listeners. See <fireMouseEvent>.
+ */
+mxGraph.prototype.mouseListeners = null;
+
+/**
+ * Variable: isMouseDown
+ *
+ * Holds the state of the mouse button.
+ */
+mxGraph.prototype.isMouseDown = false;
+
+/**
+ * Variable: model
+ *
+ * Holds the <mxGraphModel> that contains the cells to be displayed.
+ */
+mxGraph.prototype.model = null;
+
+/**
+ * Variable: view
+ *
+ * Holds the <mxGraphView> that caches the <mxCellStates> for the cells.
+ */
+mxGraph.prototype.view = null;
+
+/**
+ * Variable: stylesheet
+ *
+ * Holds the <mxStylesheet> that defines the appearance of the cells.
+ *
+ *
+ * Example:
+ *
+ * Use the following code to read a stylesheet into an existing graph.
+ *
+ * (code)
+ * var req = mxUtils.load('stylesheet.xml');
+ * var root = req.getDocumentElement();
+ * var dec = new mxCodec(root.ownerDocument);
+ * dec.decode(root, graph.stylesheet);
+ * (end)
+ */
+mxGraph.prototype.stylesheet = null;
+
+/**
+ * Variable: selectionModel
+ *
+ * Holds the <mxGraphSelectionModel> that models the current selection.
+ */
+mxGraph.prototype.selectionModel = null;
+
+/**
+ * Variable: cellEditor
+ *
+ * Holds the <mxCellEditor> that is used as the in-place editing.
+ */
+mxGraph.prototype.cellEditor = null;
+
+/**
+ * Variable: cellRenderer
+ *
+ * Holds the <mxCellRenderer> for rendering the cells in the graph.
+ */
+mxGraph.prototype.cellRenderer = null;
+
+/**
+ * Variable: multiplicities
+ *
+ * An array of <mxMultiplicities> describing the allowed
+ * connections in a graph.
+ */
+mxGraph.prototype.multiplicities = null;
+
+/**
+ * Variable: renderHint
+ *
+ * RenderHint as it was passed to the constructor.
+ */
+mxGraph.prototype.renderHint = null;
+
+/**
+ * Variable: dialect
+ *
+ * Dialect to be used for drawing the graph. Possible values are all
+ * constants in <mxConstants> with a DIALECT-prefix.
+ */
+mxGraph.prototype.dialect = null;
+
+/**
+ * Variable: gridSize
+ *
+ * Specifies the grid size. Default is 10.
+ */
+mxGraph.prototype.gridSize = 10;
+
+/**
+ * Variable: gridEnabled
+ *
+ * Specifies if the grid is enabled. This is used in <snap>. Default is
+ * true.
+ */
+mxGraph.prototype.gridEnabled = true;
+
+/**
+ * Variable: portsEnabled
+ *
+ * Specifies if ports are enabled. This is used in <cellConnected> to update
+ * the respective style. Default is true.
+ */
+mxGraph.prototype.portsEnabled = true;
+
+/**
+ * Variable: doubleTapEnabled
+ *
+ * Specifies if double taps on touch-based devices should be handled. Default
+ * is true.
+ */
+mxGraph.prototype.doubleTapEnabled = true;
+
+/**
+ * Variable: doubleTapTimeout
+ *
+ * Specifies the timeout for double taps. Default is 700 ms.
+ */
+mxGraph.prototype.doubleTapTimeout = 700;
+
+/**
+ * Variable: doubleTapTolerance
+ *
+ * Specifies the tolerance for double taps. Default is 25 pixels.
+ */
+mxGraph.prototype.doubleTapTolerance = 25;
+
+/**
+ * Variable: lastTouchX
+ *
+ * Holds the x-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchX
+ *
+ * Holds the y-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchTime
+ *
+ * Holds the time of the last touch event for double click detection.
+ */
+mxGraph.prototype.lastTouchTime = 0;
+
+/**
+ * Variable: gestureEnabled
+ *
+ * Specifies if the handleGesture method should be invoked. Default is true. This
+ * is an experimental feature for touch-based devices.
+ */
+mxGraph.prototype.gestureEnabled = true;
+
+/**
+ * Variable: tolerance
+ *
+ * Tolerance for a move to be handled as a single click.
+ * Default is 4 pixels.
+ */
+mxGraph.prototype.tolerance = 4;
+
+/**
+ * Variable: defaultOverlap
+ *
+ * Value returned by <getOverlap> if <isAllowOverlapParent> returns
+ * true for the given cell. <getOverlap> is used in <constrainChild> if
+ * <isConstrainChild> returns true. The value specifies the
+ * portion of the child which is allowed to overlap the parent.
+ */
+mxGraph.prototype.defaultOverlap = 0.5;
+
+/**
+ * Variable: defaultParent
+ *
+ * Specifies the default parent to be used to insert new cells.
+ * This is used in <getDefaultParent>. Default is null.
+ */
+mxGraph.prototype.defaultParent = null;
+
+/**
+ * Variable: alternateEdgeStyle
+ *
+ * Specifies the alternate edge style to be used if the main control point
+ * on an edge is being doubleclicked. Default is null.
+ */
+mxGraph.prototype.alternateEdgeStyle = null;
+
+/**
+ * Variable: backgroundImage
+ *
+ * Specifies the <mxImage> to be returned by <getBackgroundImage>. Default
+ * is null.
+ *
+ * Example:
+ *
+ * (code)
+ * var img = new mxImage('http://www.example.com/maps/examplemap.jpg', 1024, 768);
+ * graph.setBackgroundImage(img);
+ * graph.view.validate();
+ * (end)
+ */
+mxGraph.prototype.backgroundImage = null;
+
+/**
+ * Variable: pageVisible
+ *
+ * Specifies if the background page should be visible. Default is false.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageVisible = false;
+
+/**
+ * Variable: pageBreaksVisible
+ *
+ * Specifies if a dashed line should be drawn between multiple pages. Default
+ * is false. If you change this value while a graph is being displayed then you
+ * should call <sizeDidChange> to force an update of the display.
+ */
+mxGraph.prototype.pageBreaksVisible = false;
+
+/**
+ * Variable: pageBreakColor
+ *
+ * Specifies the color for page breaks. Default is 'gray'.
+ */
+mxGraph.prototype.pageBreakColor = 'gray';
+
+/**
+ * Variable: pageBreakDashed
+ *
+ * Specifies the page breaks should be dashed. Default is true.
+ */
+mxGraph.prototype.pageBreakDashed = true;
+
+/**
+ * Variable: minPageBreakDist
+ *
+ * Specifies the minimum distance for page breaks to be visible. Default is
+ * 20 (in pixels).
+ */
+mxGraph.prototype.minPageBreakDist = 20;
+
+/**
+ * Variable: preferPageSize
+ *
+ * Specifies if the graph size should be rounded to the next page number in
+ * <sizeDidChange>. This is only used if the graph container has scrollbars.
+ * Default is false.
+ */
+mxGraph.prototype.preferPageSize = false;
+
+/**
+ * Variable: pageFormat
+ *
+ * Specifies the page format for the background page. Default is
+ * <mxConstants.PAGE_FORMAT_A4_PORTRAIT>. This is used as the default in
+ * <mxPrintPreview> and for painting the background page if <pageVisible> is
+ * true and the pagebreaks if <pageBreaksVisible> is true.
+ */
+mxGraph.prototype.pageFormat = mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+
+/**
+ * Variable: pageScale
+ *
+ * Specifies the scale of the background page. Default is 1.5.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageScale = 1.5;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies the return value for <isEnabled>. Default is true.
+ */
+mxGraph.prototype.enabled = true;
+
+/**
+ * Variable: escapeEnabled
+ *
+ * Specifies if <mxKeyHandler> should invoke <escape> when the escape key
+ * is pressed. Default is true.
+ */
+mxGraph.prototype.escapeEnabled = true;
+
+/**
+ * Variable: invokesStopCellEditing
+ *
+ * If true, when editing is to be stopped by way of selection changing,
+ * data in diagram changing or other means stopCellEditing is invoked, and
+ * changes are saved. This is implemented in a focus handler in
+ * <mxCellEditor>. Default is true.
+ */
+mxGraph.prototype.invokesStopCellEditing = true;
+
+/**
+ * Variable: enterStopsCellEditing
+ *
+ * If true, pressing the enter key without pressing control or shift will stop
+ * editing and accept the new value. This is used in <mxCellEditor> to stop
+ * cell editing. Note: You can always use F2 and escape to stop editing.
+ * Default is false.
+ */
+mxGraph.prototype.enterStopsCellEditing = false;
+
+/**
+ * Variable: useScrollbarsForPanning
+ *
+ * Specifies if scrollbars should be used for panning in <panGraph> if
+ * any scrollbars are available. If scrollbars are enabled in CSS, but no
+ * scrollbars appear because the graph is smaller than the container size,
+ * then no panning occurs if this is true. Default is true.
+ */
+mxGraph.prototype.useScrollbarsForPanning = true;
+
+/**
+ * Variable: exportEnabled
+ *
+ * Specifies the return value for <canExportCell>. Default is true.
+ */
+mxGraph.prototype.exportEnabled = true;
+
+/**
+ * Variable: importEnabled
+ *
+ * Specifies the return value for <canImportCell>. Default is true.
+ */
+mxGraph.prototype.importEnabled = true;
+
+/**
+ * Variable: cellsLocked
+ *
+ * Specifies the return value for <isCellLocked>. Default is false.
+ */
+mxGraph.prototype.cellsLocked = false;
+
+/**
+ * Variable: cellsCloneable
+ *
+ * Specifies the return value for <isCellCloneable>. Default is true.
+ */
+mxGraph.prototype.cellsCloneable = true;
+
+/**
+ * Variable: foldingEnabled
+ *
+ * Specifies if folding (collapse and expand via an image icon in the graph
+ * should be enabled). Default is true.
+ */
+mxGraph.prototype.foldingEnabled = true;
+
+/**
+ * Variable: cellsEditable
+ *
+ * Specifies the return value for <isCellEditable>. Default is true.
+ */
+mxGraph.prototype.cellsEditable = true;
+
+/**
+ * Variable: cellsDeletable
+ *
+ * Specifies the return value for <isCellDeletable>. Default is true.
+ */
+mxGraph.prototype.cellsDeletable = true;
+
+/**
+ * Variable: cellsMovable
+ *
+ * Specifies the return value for <isCellMovable>. Default is true.
+ */
+mxGraph.prototype.cellsMovable = true;
+
+/**
+ * Variable: edgeLabelsMovable
+ *
+ * Specifies the return value for edges in <isLabelMovable>. Default is true.
+ */
+mxGraph.prototype.edgeLabelsMovable = true;
+
+/**
+ * Variable: vertexLabelsMovable
+ *
+ * Specifies the return value for vertices in <isLabelMovable>. Default is false.
+ */
+mxGraph.prototype.vertexLabelsMovable = false;
+
+/**
+ * Variable: dropEnabled
+ *
+ * Specifies the return value for <isDropEnabled>. Default is false.
+ */
+mxGraph.prototype.dropEnabled = false;
+
+/**
+ * Variable: splitEnabled
+ *
+ * Specifies if dropping onto edges should be enabled. Default is true.
+ */
+mxGraph.prototype.splitEnabled = true;
+
+/**
+ * Variable: cellsResizable
+ *
+ * Specifies the return value for <isCellResizable>. Default is true.
+ */
+mxGraph.prototype.cellsResizable = true;
+
+/**
+ * Variable: cellsBendable
+ *
+ * Specifies the return value for <isCellsBendable>. Default is true.
+ */
+mxGraph.prototype.cellsBendable = true;
+
+/**
+ * Variable: cellsSelectable
+ *
+ * Specifies the return value for <isCellSelectable>. Default is true.
+ */
+mxGraph.prototype.cellsSelectable = true;
+
+/**
+ * Variable: cellsDisconnectable
+ *
+ * Specifies the return value for <isCellDisconntable>. Default is true.
+ */
+mxGraph.prototype.cellsDisconnectable = true;
+
+/**
+ * Variable: autoSizeCells
+ *
+ * Specifies if the graph should automatically update the cell size after an
+ * edit. This is used in <isAutoSizeCell>. Default is false.
+ */
+mxGraph.prototype.autoSizeCells = false;
+
+/**
+ * Variable: autoScroll
+ *
+ * Specifies if the graph should automatically scroll if the mouse goes near
+ * the container edge while dragging. This is only taken into account if the
+ * container has scrollbars. Default is true.
+ *
+ * If you need this to work without scrollbars then set <ignoreScrollbars> to
+ * true.
+ */
+mxGraph.prototype.autoScroll = true;
+
+/**
+ * Variable: timerAutoScroll
+ *
+ * Specifies if timer-based autoscrolling should be used via mxPanningManager.
+ * Note that this disables the code in <scrollPointToVisible> and uses code in
+ * mxPanningManager instead. Note that <autoExtend> is disabled if this is
+ * true and that this should only be used with a scroll buffer or when
+ * scollbars are visible and scrollable in all directions. Default is false.
+ */
+mxGraph.prototype.timerAutoScroll = false;
+
+/**
+ * Variable: allowAutoPanning
+ *
+ * Specifies if panning via <panGraph> should be allowed to implement autoscroll
+ * if no scrollbars are available in <scrollPointToVisible>. Default is false.
+ */
+mxGraph.prototype.allowAutoPanning = false;
+
+/**
+ * Variable: ignoreScrollbars
+ *
+ * Specifies if the graph should automatically scroll regardless of the
+ * scrollbars.
+ */
+mxGraph.prototype.ignoreScrollbars = false;
+
+/**
+ * Variable: autoExtend
+ *
+ * Specifies if the size of the graph should be automatically extended if the
+ * mouse goes near the container edge while dragging. This is only taken into
+ * account if the container has scrollbars. Default is true. See <autoScroll>.
+ */
+mxGraph.prototype.autoExtend = true;
+
+/**
+ * Variable: maximumGraphBounds
+ *
+ * <mxRectangle> that specifies the area in which all cells in the diagram
+ * should be placed. Uses in <getMaximumGraphBounds>. Use a width or height of
+ * 0 if you only want to give a upper, left corner.
+ */
+mxGraph.prototype.maximumGraphBounds = null;
+
+/**
+ * Variable: minimumGraphSize
+ *
+ * <mxRectangle> that specifies the minimum size of the graph. This is ignored
+ * if the graph container has no scrollbars. Default is null.
+ */
+mxGraph.prototype.minimumGraphSize = null;
+
+/**
+ * Variable: minimumContainerSize
+ *
+ * <mxRectangle> that specifies the minimum size of the <container> if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.minimumContainerSize = null;
+
+/**
+ * Variable: maximumContainerSize
+ *
+ * <mxRectangle> that specifies the maximum size of the container if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.maximumContainerSize = null;
+
+/**
+ * Variable: resizeContainer
+ *
+ * Specifies if the container should be resized to the graph size when
+ * the graph size has changed. Default is false.
+ */
+mxGraph.prototype.resizeContainer = false;
+
+/**
+ * Variable: border
+ *
+ * Border to be added to the bottom and right side when the container is
+ * being resized after the graph has been changed. Default is 0.
+ */
+mxGraph.prototype.border = 0;
+
+/**
+ * Variable: ordered
+ *
+ * Specifies if the display should reflect the order of the cells in
+ * the model. Default is true. This has precendence over
+ * <keepEdgesInBackground> and <keepEdgesInForeground>.
+ */
+mxGraph.prototype.ordered = true;
+
+/**
+ * Variable: keepEdgesInForeground
+ *
+ * Specifies if edges should appear in the foreground regardless of their
+ * order in the model. This has precendence over <keepEdgeInBackground>,
+ * but not over <ordered>. Default is false.
+ */
+mxGraph.prototype.keepEdgesInForeground = false;
+
+/**
+ * Variable: keepEdgesInBackground
+ *
+ * Specifies if edges should appear in the background regardless of their
+ * order in the model. <ordered> and <keepEdgesInForeground> have
+ * precedence over this setting. Default is true.
+ */
+mxGraph.prototype.keepEdgesInBackground = true;
+
+/**
+ * Variable: allowNegativeCoordinates
+ *
+ * Specifies if negative coordinates for vertices are allowed. Default is true.
+ */
+mxGraph.prototype.allowNegativeCoordinates = true;
+
+/**
+ * Variable: constrainChildren
+ *
+ * Specifies the return value for <isConstrainChildren>. Default is
+ * true.
+ */
+mxGraph.prototype.constrainChildren = true;
+
+/**
+ * Variable: extendParents
+ *
+ * Specifies if a parent should contain the child bounds after a resize of
+ * the child. Default is true.
+ */
+mxGraph.prototype.extendParents = true;
+
+/**
+ * Variable: extendParentsOnAdd
+ *
+ * Specifies if parents should be extended according to the <extendParents>
+ * switch if cells are added. Default is true.
+ */
+mxGraph.prototype.extendParentsOnAdd = true;
+
+/**
+ * Variable: collapseToPreferredSize
+ *
+ * Specifies if the cell size should be changed to the preferred size when
+ * a cell is first collapsed. Default is true.
+ */
+mxGraph.prototype.collapseToPreferredSize = true;
+
+/**
+ * Variable: zoomFactor
+ *
+ * Specifies the factor used for <zoomIn> and <zoomOut>. Default is 1.2
+ * (120%).
+ */
+mxGraph.prototype.zoomFactor = 1.2;
+
+/**
+ * Variable: keepSelectionVisibleOnZoom
+ *
+ * Specifies if the viewport should automatically contain the selection cells
+ * after a zoom operation. Default is false.
+ */
+mxGraph.prototype.keepSelectionVisibleOnZoom = false;
+
+/**
+ * Variable: centerZoom
+ *
+ * Specifies if the zoom operations should go into the center of the actual
+ * diagram rather than going from top, left. Default is true.
+ */
+mxGraph.prototype.centerZoom = true;
+
+/**
+ * Variable: resetViewOnRootChange
+ *
+ * Specifies if the scale and translate should be reset if the root changes in
+ * the model. Default is true.
+ */
+mxGraph.prototype.resetViewOnRootChange = true;
+
+/**
+ * Variable: resetEdgesOnResize
+ *
+ * Specifies if edge control points should be reset after the resize of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnResize = false;
+
+/**
+ * Variable: resetEdgesOnMove
+ *
+ * Specifies if edge control points should be reset after the move of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnMove = false;
+
+/**
+ * Variable: resetEdgesOnConnect
+ *
+ * Specifies if edge control points should be reset after the the edge has been
+ * reconnected. Default is true.
+ */
+mxGraph.prototype.resetEdgesOnConnect = true;
+
+/**
+ * Variable: allowLoops
+ *
+ * Specifies if loops (aka self-references) are allowed. Default is false.
+ */
+mxGraph.prototype.allowLoops = false;
+
+/**
+ * Variable: defaultLoopStyle
+ *
+ * <mxEdgeStyle> to be used for loops. This is a fallback for loops if the
+ * <mxConstants.STYLE_LOOP> is undefined. Default is <mxEdgeStyle.Loop>.
+ */
+mxGraph.prototype.defaultLoopStyle = mxEdgeStyle.Loop;
+
+/**
+ * Variable: multigraph
+ *
+ * Specifies if multiple edges in the same direction between the same pair of
+ * vertices are allowed. Default is true.
+ */
+mxGraph.prototype.multigraph = true;
+
+/**
+ * Variable: connectableEdges
+ *
+ * Specifies if edges are connectable. Default is false. This overrides the
+ * connectable field in edges.
+ */
+mxGraph.prototype.connectableEdges = false;
+
+/**
+ * Variable: allowDanglingEdges
+ *
+ * Specifies if edges with disconnected terminals are allowed in the graph.
+ * Default is true.
+ */
+mxGraph.prototype.allowDanglingEdges = true;
+
+/**
+ * Variable: cloneInvalidEdges
+ *
+ * Specifies if edges that are cloned should be validated and only inserted
+ * if they are valid. Default is true.
+ */
+mxGraph.prototype.cloneInvalidEdges = false;
+
+/**
+ * Variable: disconnectOnMove
+ *
+ * Specifies if edges should be disconnected from their terminals when they
+ * are moved. Default is true.
+ */
+mxGraph.prototype.disconnectOnMove = true;
+
+/**
+ * Variable: labelsVisible
+ *
+ * Specifies if labels should be visible. This is used in <getLabel>. Default
+ * is true.
+ */
+mxGraph.prototype.labelsVisible = true;
+
+/**
+ * Variable: htmlLabels
+ *
+ * Specifies the return value for <isHtmlLabel>. Default is false.
+ */
+mxGraph.prototype.htmlLabels = false;
+
+/**
+ * Variable: swimlaneSelectionEnabled
+ *
+ * Specifies if swimlanes should be selectable via the content if the
+ * mouse is released. Default is true.
+ */
+mxGraph.prototype.swimlaneSelectionEnabled = true;
+
+/**
+ * Variable: swimlaneNesting
+ *
+ * Specifies if nesting of swimlanes is allowed. Default is true.
+ */
+mxGraph.prototype.swimlaneNesting = true;
+
+/**
+ * Variable: swimlaneIndicatorColorAttribute
+ *
+ * The attribute used to find the color for the indicator if the indicator
+ * color is set to 'swimlane'. Default is <mxConstants.STYLE_FILLCOLOR>.
+ */
+mxGraph.prototype.swimlaneIndicatorColorAttribute = mxConstants.STYLE_FILLCOLOR;
+
+/**
+ * Variable: imageBundles
+ *
+ * Holds the list of image bundles.
+ */
+mxGraph.prototype.imageBundles = null;
+
+/**
+ * Variable: minFitScale
+ *
+ * Specifies the minimum scale to be applied in <fit>. Default is 0.1. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.minFitScale = 0.1;
+
+/**
+ * Variable: maxFitScale
+ *
+ * Specifies the maximum scale to be applied in <fit>. Default is 8. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.maxFitScale = 8;
+
+/**
+ * Variable: panDx
+ *
+ * Current horizontal panning value. Default is 0.
+ */
+mxGraph.prototype.panDx = 0;
+
+/**
+ * Variable: panDy
+ *
+ * Current vertical panning value. Default is 0.
+ */
+mxGraph.prototype.panDy = 0;
+
+/**
+ * Variable: collapsedImage
+ *
+ * Specifies the <mxImage> to indicate a collapsed state.
+ * Default value is mxClient.imageBasePath + '/collapsed.gif'
+ */
+mxGraph.prototype.collapsedImage = new mxImage(mxClient.imageBasePath + '/collapsed.gif', 9, 9);
+
+/**
+ * Variable: expandedImage
+ *
+ * Specifies the <mxImage> to indicate a expanded state.
+ * Default value is mxClient.imageBasePath + '/expanded.gif'
+ */
+mxGraph.prototype.expandedImage = new mxImage(mxClient.imageBasePath + '/expanded.gif', 9, 9);
+
+/**
+ * Variable: warningImage
+ *
+ * Specifies the <mxImage> for the image to be used to display a warning
+ * overlay. See <setCellWarning>. Default value is mxClient.imageBasePath +
+ * '/warning'. The extension for the image depends on the platform. It is
+ * '.png' on the Mac and '.gif' on all other platforms.
+ */
+mxGraph.prototype.warningImage = new mxImage(mxClient.imageBasePath + '/warning'+
+ ((mxClient.IS_MAC) ? '.png' : '.gif'), 16, 16);
+
+/**
+ * Variable: alreadyConnectedResource
+ *
+ * Specifies the resource key for the error message to be displayed in
+ * non-multigraphs when two vertices are already connected. If the resource
+ * for this key does not exist then the value is used as the error message.
+ * Default is 'alreadyConnected'.
+ */
+mxGraph.prototype.alreadyConnectedResource = (mxClient.language != 'none') ? 'alreadyConnected' : '';
+
+/**
+ * Variable: containsValidationErrorsResource
+ *
+ * Specifies the resource key for the warning message to be displayed when
+ * a collapsed cell contains validation errors. If the resource for this
+ * key does not exist then the value is used as the warning message.
+ * Default is 'containsValidationErrors'.
+ */
+mxGraph.prototype.containsValidationErrorsResource = (mxClient.language != 'none') ? 'containsValidationErrors' : '';
+
+/**
+ * Variable: collapseExpandResource
+ *
+ * Specifies the resource key for the tooltip on the collapse/expand icon.
+ * If the resource for this key does not exist then the value is used as
+ * the tooltip. Default is 'collapse-expand'.
+ */
+mxGraph.prototype.collapseExpandResource = (mxClient.language != 'none') ? 'collapse-expand' : '';
+
+/**
+ * Function: init
+ *
+ * Initializes the <container> and creates the respective datastructures.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the graph display.
+ */
+ mxGraph.prototype.init = function(container)
+ {
+ this.container = container;
+
+ // Initializes the in-place editor
+ this.cellEditor = this.createCellEditor();
+
+ // Initializes the container using the view
+ this.view.init();
+
+ // Updates the size of the container for the current graph
+ this.sizeDidChange();
+
+ // Automatic deallocation of memory
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+ {
+ this.destroy();
+ }));
+
+ // Disable shift-click for text
+ mxEvent.addListener(container, 'selectstart',
+ mxUtils.bind(this, function()
+ {
+ return this.isEditing();
+ })
+ );
+ }
+};
+
+/**
+ * Function: createHandlers
+ *
+ * Creates the tooltip-, panning-, connection- and graph-handler (in this
+ * order). This is called in the constructor before <init> is called.
+ */
+mxGraph.prototype.createHandlers = function(container)
+{
+ this.tooltipHandler = new mxTooltipHandler(this);
+ this.tooltipHandler.setEnabled(false);
+ this.panningHandler = new mxPanningHandler(this);
+ this.panningHandler.panningEnabled = false;
+ this.selectionCellsHandler = new mxSelectionCellsHandler(this);
+ this.connectionHandler = new mxConnectionHandler(this);
+ this.connectionHandler.setEnabled(false);
+ this.graphHandler = new mxGraphHandler(this);
+};
+
+/**
+ * Function: createSelectionModel
+ *
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createSelectionModel = function()
+{
+ return new mxGraphSelectionModel(this);
+};
+
+/**
+ * Function: createStylesheet
+ *
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createStylesheet = function()
+{
+ return new mxStylesheet();
+};
+
+/**
+ * Function: createGraphView
+ *
+ * Creates a new <mxGraphView> to be used in this graph.
+ */
+mxGraph.prototype.createGraphView = function()
+{
+ return new mxGraphView(this);
+};
+
+/**
+ * Function: createCellRenderer
+ *
+ * Creates a new <mxCellRenderer> to be used in this graph.
+ */
+mxGraph.prototype.createCellRenderer = function()
+{
+ return new mxCellRenderer();
+};
+
+/**
+ * Function: createCellEditor
+ *
+ * Creates a new <mxCellEditor> to be used in this graph.
+ */
+mxGraph.prototype.createCellEditor = function()
+{
+ return new mxCellEditor(this);
+};
+
+/**
+ * Function: getModel
+ *
+ * Returns the <mxGraphModel> that contains the cells.
+ */
+mxGraph.prototype.getModel = function()
+{
+ return this.model;
+};
+
+/**
+ * Function: getView
+ *
+ * Returns the <mxGraphView> that contains the <mxCellStates>.
+ */
+mxGraph.prototype.getView = function()
+{
+ return this.view;
+};
+
+/**
+ * Function: getStylesheet
+ *
+ * Returns the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.getStylesheet = function()
+{
+ return this.stylesheet;
+};
+
+/**
+ * Function: setStylesheet
+ *
+ * Sets the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.setStylesheet = function(stylesheet)
+{
+ this.stylesheet = stylesheet;
+};
+
+/**
+ * Function: getSelectionModel
+ *
+ * Returns the <mxGraphSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.getSelectionModel = function()
+{
+ return this.selectionModel;
+};
+
+/**
+ * Function: setSelectionModel
+ *
+ * Sets the <mxSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.setSelectionModel = function(selectionModel)
+{
+ this.selectionModel = selectionModel;
+};
+
+/**
+ * Function: getSelectionCellsForChanges
+ *
+ * Returns the cells to be selected for the given array of changes.
+ */
+mxGraph.prototype.getSelectionCellsForChanges = function(changes)
+{
+ var cells = [];
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ if (change.constructor != mxRootChange)
+ {
+ var cell = null;
+
+ if (change instanceof mxChildChange && change.previous == null)
+ {
+ cell = change.child;
+ }
+ else if (change.cell != null && change.cell instanceof mxCell)
+ {
+ cell = change.cell;
+ }
+
+ if (cell != null && mxUtils.indexOf(cells, cell) < 0)
+ {
+ cells.push(cell);
+ }
+ }
+ }
+
+ return this.getModel().getTopmostCells(cells);
+};
+
+/**
+ * Function: graphModelChanged
+ *
+ * Called when the graph model changes. Invokes <processChange> on each
+ * item of the given array to update the view accordingly.
+ *
+ * Parameters:
+ *
+ * changes - Array that contains the individual changes.
+ */
+mxGraph.prototype.graphModelChanged = function(changes)
+{
+ for (var i = 0; i < changes.length; i++)
+ {
+ this.processChange(changes[i]);
+ }
+
+ this.removeSelectionCells(this.getRemovedCellsForChanges(changes));
+
+ this.view.validate();
+ this.sizeDidChange();
+};
+
+/**
+ * Function: getRemovedCellsForChanges
+ *
+ * Returns the cells that have been removed from the model.
+ */
+mxGraph.prototype.getRemovedCellsForChanges = function(changes)
+{
+ var result = [];
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ // Resets the view settings, removes all cells and clears
+ // the selection if the root changes.
+ if (change instanceof mxRootChange)
+ {
+ break;
+ }
+ else if (change instanceof mxChildChange)
+ {
+ if (change.previous != null && change.parent == null)
+ {
+ result = result.concat(this.model.getDescendants(change.child));
+ }
+ }
+ else if (change instanceof mxVisibleChange)
+ {
+ result = result.concat(this.model.getDescendants(change.cell));
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: processChange
+ *
+ * Processes the given change and invalidates the respective cached data
+ * in <view>. This fires a <root> event if the root has changed in the
+ * model.
+ *
+ * Parameters:
+ *
+ * change - Object that represents the change on the model.
+ */
+mxGraph.prototype.processChange = function(change)
+{
+ // Resets the view settings, removes all cells and clears
+ // the selection if the root changes.
+ if (change instanceof mxRootChange)
+ {
+ this.clearSelection();
+ this.removeStateForCell(change.previous);
+
+ if (this.resetViewOnRootChange)
+ {
+ this.view.scale = 1;
+ this.view.translate.x = 0;
+ this.view.translate.y = 0;
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.ROOT));
+ }
+
+ // Adds or removes a child to the view by online invaliding
+ // the minimal required portions of the cache, namely, the
+ // old and new parent and the child.
+ else if (change instanceof mxChildChange)
+ {
+ var newParent = this.model.getParent(change.child);
+
+ if (newParent != null)
+ {
+ // Flags the cell for updating the order in the renderer
+ this.view.invalidate(change.child, true, false, change.previous != null);
+ }
+ else
+ {
+ this.removeStateForCell(change.child);
+
+ // Handles special case of current root of view being removed
+ if (this.view.currentRoot == change.child)
+ {
+ this.home();
+ }
+ }
+
+ if (newParent != change.previous)
+ {
+ // Refreshes the collapse/expand icons on the parents
+ if (newParent != null)
+ {
+ this.view.invalidate(newParent, false, false);
+ }
+
+ if (change.previous != null)
+ {
+ this.view.invalidate(change.previous, false, false);
+ }
+ }
+ }
+
+ // Handles two special cases where the shape does not need to be
+ // recreated from scratch, it only need to be invalidated.
+ else if (change instanceof mxTerminalChange ||
+ change instanceof mxGeometryChange)
+ {
+ this.view.invalidate(change.cell);
+ }
+
+ // Handles two special cases where only the shape, but no
+ // descendants need to be recreated
+ else if (change instanceof mxValueChange)
+ {
+ this.view.invalidate(change.cell, false, false);
+ }
+
+ // Requires a new mxShape in JavaScript
+ else if (change instanceof mxStyleChange)
+ {
+ this.view.invalidate(change.cell, true, true, false);
+ this.view.removeState(change.cell);
+ }
+
+ // Removes the state from the cache by default
+ else if (change.cell != null &&
+ change.cell instanceof mxCell)
+ {
+ this.removeStateForCell(change.cell);
+ }
+};
+
+/**
+ * Function: removeStateForCell
+ *
+ * Removes all cached information for the given cell and its descendants.
+ * This is called when a cell was removed from the model.
+ *
+ * Paramters:
+ *
+ * cell - <mxCell> that was removed from the model.
+ */
+mxGraph.prototype.removeStateForCell = function(cell)
+{
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.removeStateForCell(this.model.getChildAt(cell, i));
+ }
+
+ this.view.removeState(cell);
+};
+
+/**
+ * Group: Overlays
+ */
+
+/**
+ * Function: addCellOverlay
+ *
+ * Adds an <mxCellOverlay> for the specified cell. This method fires an
+ * <addoverlay> event and returns the new <mxCellOverlay>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to add the overlay for.
+ * overlay - <mxCellOverlay> to be added for the cell.
+ */
+mxGraph.prototype.addCellOverlay = function(cell, overlay)
+{
+ if (cell.overlays == null)
+ {
+ cell.overlays = [];
+ }
+
+ cell.overlays.push(overlay);
+
+ var state = this.view.getState(cell);
+
+ // Immediately updates the cell display if the state exists
+ if (state != null)
+ {
+ this.cellRenderer.redraw(state);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY,
+ 'cell', cell, 'overlay', overlay));
+
+ return overlay;
+};
+
+/**
+ * Function: getCellOverlays
+ *
+ * Returns the array of <mxCellOverlays> for the given cell or null, if
+ * no overlays are defined.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose overlays should be returned.
+ */
+mxGraph.prototype.getCellOverlays = function(cell)
+{
+ return cell.overlays;
+};
+
+/**
+ * Function: removeCellOverlay
+ *
+ * Removes and returns the given <mxCellOverlay> from the given cell. This
+ * method fires a <removeoverlay> event. If no overlay is given, then all
+ * overlays are removed using <removeOverlays>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose overlay should be removed.
+ * overlay - Optional <mxCellOverlay> to be removed.
+ */
+mxGraph.prototype.removeCellOverlay = function(cell, overlay)
+{
+ if (overlay == null)
+ {
+ this.removeCellOverlays(cell);
+ }
+ else
+ {
+ var index = mxUtils.indexOf(cell.overlays, overlay);
+
+ if (index >= 0)
+ {
+ cell.overlays.splice(index, 1);
+
+ if (cell.overlays.length == 0)
+ {
+ cell.overlays = null;
+ }
+
+ // Immediately updates the cell display if the state exists
+ var state = this.view.getState(cell);
+
+ if (state != null)
+ {
+ this.cellRenderer.redraw(state);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+ 'cell', cell, 'overlay', overlay));
+ }
+ else
+ {
+ overlay = null;
+ }
+ }
+
+ return overlay;
+};
+
+/**
+ * Function: removeCellOverlays
+ *
+ * Removes all <mxCellOverlays> from the given cell. This method
+ * fires a <removeoverlay> event for each <mxCellOverlay> and returns
+ * the array of <mxCellOverlays> that was removed from the cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose overlays should be removed
+ */
+mxGraph.prototype.removeCellOverlays = function(cell)
+{
+ var overlays = cell.overlays;
+
+ if (overlays != null)
+ {
+ cell.overlays = null;
+
+ // Immediately updates the cell display if the state exists
+ var state = this.view.getState(cell);
+
+ if (state != null)
+ {
+ this.cellRenderer.redraw(state);
+ }
+
+ for (var i = 0; i < overlays.length; i++)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+ 'cell', cell, 'overlay', overlays[i]));
+ }
+ }
+
+ return overlays;
+};
+
+/**
+ * Function: clearCellOverlays
+ *
+ * Removes all <mxCellOverlays> in the graph for the given cell and all its
+ * descendants. If no cell is specified then all overlays are removed from
+ * the graph. This implementation uses <removeCellOverlays> to remove the
+ * overlays from the individual cells.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> that represents the root of the subtree to
+ * remove the overlays from. Default is the root in the model.
+ */
+mxGraph.prototype.clearCellOverlays = function(cell)
+{
+ cell = (cell != null) ? cell : this.model.getRoot();
+ this.removeCellOverlays(cell);
+
+ // Recursively removes all overlays from the children
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(cell, i);
+ this.clearCellOverlays(child); // recurse
+ }
+};
+
+/**
+ * Function: setCellWarning
+ *
+ * Creates an overlay for the given cell using the warning and image or
+ * <warningImage> and returns the new <mxCellOverlay>. The warning is
+ * displayed as a tooltip in a red font and may contain HTML markup. If
+ * the warning is null or a zero length string, then all overlays are
+ * removed from the cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.setCellWarning(cell, '<b>Warning:</b>: Hello, World!');
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose warning should be set.
+ * warning - String that represents the warning to be displayed.
+ * img - Optional <mxImage> to be used for the overlay. Default is
+ * <warningImage>.
+ * isSelect - Optional boolean indicating if a click on the overlay
+ * should select the corresponding cell. Default is false.
+ */
+mxGraph.prototype.setCellWarning = function(cell, warning, img, isSelect)
+{
+ if (warning != null && warning.length > 0)
+ {
+ img = (img != null) ? img : this.warningImage;
+
+ // Creates the overlay with the image and warning
+ var overlay = new mxCellOverlay(img,
+ '<font color=red>'+warning+'</font>');
+
+ // Adds a handler for single mouseclicks to select the cell
+ if (isSelect)
+ {
+ overlay.addListener(mxEvent.CLICK,
+ mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.setSelectionCell(cell);
+ }
+ })
+ );
+ }
+
+ // Sets and returns the overlay in the graph
+ return this.addCellOverlay(cell, overlay);
+ }
+ else
+ {
+ this.removeCellOverlays(cell);
+ }
+
+ return null;
+};
+
+/**
+ * Group: In-place editing
+ */
+
+/**
+ * Function: startEditing
+ *
+ * Calls <startEditingAtCell> using the given cell or the first selection
+ * cell.
+ *
+ * Parameters:
+ *
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditing = function(evt)
+{
+ this.startEditingAtCell(null, evt);
+};
+
+/**
+ * Function: startEditingAtCell
+ *
+ * Fires a <startEditing> event and invokes <mxCellEditor.startEditing>
+ * on <editor>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to start the in-place editor for.
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditingAtCell = function(cell, evt)
+{
+ if (cell == null)
+ {
+ cell = this.getSelectionCell();
+
+ if (cell != null && !this.isCellEditable(cell))
+ {
+ cell = null;
+ }
+ }
+
+ if (cell != null)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.START_EDITING,
+ 'cell', cell, 'event', evt));
+ this.cellEditor.startEditing(cell, evt);
+ }
+};
+
+/**
+ * Function: getEditingValue
+ *
+ * Returns the initial value for in-place editing. This implementation
+ * returns <convertValueToString> for the given cell. If this function is
+ * overridden, then <mxGraphModel.valueForCellChanged> should take care
+ * of correctly storing the actual new value inside the user object.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the initial editing value should be returned.
+ * evt - Optional mouse event that triggered the editor.
+ */
+mxGraph.prototype.getEditingValue = function(cell, evt)
+{
+ return this.convertValueToString(cell);
+};
+
+/**
+ * Function: stopEditing
+ *
+ * Stops the current editing.
+ *
+ * Parameters:
+ *
+ * cancel - Boolean that specifies if the current editing value
+ * should be stored.
+ */
+mxGraph.prototype.stopEditing = function(cancel)
+{
+ this.cellEditor.stopEditing(cancel);
+};
+
+/**
+ * Function: labelChanged
+ *
+ * Sets the label of the specified cell to the given value using
+ * <cellLabelChanged> and fires <mxEvent.LABEL_CHANGED> while the
+ * transaction is in progress. Returns the cell whose label was changed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * evt - Optional event that triggered the change.
+ */
+mxGraph.prototype.labelChanged = function(cell, value, evt)
+{
+ this.model.beginUpdate();
+ try
+ {
+ this.cellLabelChanged(cell, value, this.isAutoSizeCell(cell));
+ this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,
+ 'cell', cell, 'value', value, 'event', evt));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cell;
+};
+
+/**
+ * Function: cellLabelChanged
+ *
+ * Sets the new label for a cell. If autoSize is true then
+ * <cellSizeUpdated> will be called.
+ *
+ * In the following example, the function is extended to map changes to
+ * attributes in an XML node, as shown in <convertValueToString>.
+ * Alternatively, the handling of this can be implemented as shown in
+ * <mxGraphModel.valueForCellChanged> without the need to clone the
+ * user object.
+ *
+ * (code)
+ * var graphCellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ * // Cloned for correct undo/redo
+ * var elt = cell.value.cloneNode(true);
+ * elt.setAttribute('label', newValue);
+ *
+ * newValue = elt;
+ * graphCellLabelChanged.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * autoSize - Boolean that specifies if <cellSizeUpdated> should be called.
+ */
+mxGraph.prototype.cellLabelChanged = function(cell, value, autoSize)
+{
+ this.model.beginUpdate();
+ try
+ {
+ this.model.setValue(cell, value);
+
+ if (autoSize)
+ {
+ this.cellSizeUpdated(cell, false);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+};
+
+/**
+ * Group: Event processing
+ */
+
+/**
+ * Function: escape
+ *
+ * Processes an escape keystroke.
+ *
+ * Parameters:
+ *
+ * evt - Mouseevent that represents the keystroke.
+ */
+mxGraph.prototype.escape = function(evt)
+{
+ this.stopEditing(true);
+ this.connectionHandler.reset();
+ this.graphHandler.reset();
+
+ // Cancels all cell-based editing
+ var cells = this.getSelectionCells();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var state = this.view.getState(cells[i]);
+
+ if (state != null && state.handler != null)
+ {
+ state.handler.reset();
+ }
+ }
+};
+
+/**
+ * Function: click
+ *
+ * Processes a singleclick on an optional cell and fires a <click> event.
+ * The click event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then the cell is selected using
+ * <selectCellForEvent> or the selection is cleared using
+ * <clearSelection>. The events consumed state is set to true if the
+ * corresponding <mxMouseEvent> has been consumed.
+ *
+ * Parameters:
+ *
+ * me - <mxMouseEvent> that represents the single click.
+ */
+mxGraph.prototype.click = function(me)
+{
+ var evt = me.getEvent();
+ var cell = me.getCell();
+ var mxe = new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', cell);
+
+ if (me.isConsumed())
+ {
+ mxe.consume();
+ }
+
+ this.fireEvent(mxe);
+
+ // Handles the event if it has not been consumed
+ if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+ {
+ if (cell != null)
+ {
+ this.selectCellForEvent(cell, evt);
+ }
+ else
+ {
+ var swimlane = null;
+
+ if (this.isSwimlaneSelectionEnabled())
+ {
+ // Gets the swimlane at the location (includes
+ // content area of swimlanes)
+ swimlane = this.getSwimlaneAt(me.getGraphX(), me.getGraphY());
+ }
+
+ // Selects the swimlane and consumes the event
+ if (swimlane != null)
+ {
+ this.selectCellForEvent(swimlane, evt);
+ }
+
+ // Ignores the event if the control key is pressed
+ else if (!this.isToggleEvent(evt))
+ {
+ this.clearSelection();
+ }
+ }
+ }
+};
+
+/**
+ * Function: dblClick
+ *
+ * Processes a doubleclick on an optional cell and fires a <dblclick>
+ * event. The event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then <edit> is called with the given
+ * cell. The event is ignored if no cell was specified.
+ *
+ * Example for overriding this method.
+ *
+ * (code)
+ * graph.dblClick = function(evt, cell)
+ * {
+ * var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+ * this.fireEvent(mxe);
+ *
+ * if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+ * {
+ * mxUtils.alert('Hello, World!');
+ * mxe.consume();
+ * }
+ * }
+ * (end)
+ *
+ * Example listener for this event.
+ *
+ * (code)
+ * graph.addListener(mxEvent.DOUBLE_CLICK, function(sender, evt)
+ * {
+ * var cell = evt.getProperty('cell');
+ * // do something with the cell...
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * evt - Mouseevent that represents the doubleclick.
+ * cell - Optional <mxCell> under the mousepointer.
+ */
+mxGraph.prototype.dblClick = function(evt, cell)
+{
+ var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+ this.fireEvent(mxe);
+
+ // Handles the event if it has not been consumed
+ if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() &&
+ cell != null && this.isCellEditable(cell))
+ {
+ this.startEditingAtCell(cell, evt);
+ }
+};
+
+/**
+ * Function: scrollPointToVisible
+ *
+ * Scrolls the graph to the given point, extending the graph container if
+ * specified.
+ */
+mxGraph.prototype.scrollPointToVisible = function(x, y, extend, border)
+{
+ if (!this.timerAutoScroll && (this.ignoreScrollbars || mxUtils.hasScrollbars(this.container)))
+ {
+ var c = this.container;
+ border = (border != null) ? border : 20;
+
+ if (x >= c.scrollLeft && y >= c.scrollTop && x <= c.scrollLeft + c.clientWidth &&
+ y <= c.scrollTop + c.clientHeight)
+ {
+ var dx = c.scrollLeft + c.clientWidth - x;
+
+ if (dx < border)
+ {
+ var old = c.scrollLeft;
+ c.scrollLeft += border - dx;
+
+ // Automatically extends the canvas size to the bottom, right
+ // if the event is outside of the canvas and the edge of the
+ // canvas has been reached. Notes: Needs fix for IE.
+ if (extend && old == c.scrollLeft)
+ {
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var root = this.view.getDrawPane().ownerSVGElement;
+ var width = this.container.scrollWidth + border - dx;
+
+ // Updates the clipping region. This is an expensive
+ // operation that should not be executed too often.
+ root.setAttribute('width', width);
+ }
+ else
+ {
+ var width = Math.max(c.clientWidth, c.scrollWidth) + border - dx;
+ var canvas = this.view.getCanvas();
+ canvas.style.width = width + 'px';
+ }
+
+ c.scrollLeft += border - dx;
+ }
+ }
+ else
+ {
+ dx = x - c.scrollLeft;
+
+ if (dx < border)
+ {
+ c.scrollLeft -= border - dx;
+ }
+ }
+
+ var dy = c.scrollTop + c.clientHeight - y;
+
+ if (dy < border)
+ {
+ var old = c.scrollTop;
+ c.scrollTop += border - dy;
+
+ if (old == c.scrollTop && extend)
+ {
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var root = this.view.getDrawPane().ownerSVGElement;
+ var height = this.container.scrollHeight + border - dy;
+
+ // Updates the clipping region. This is an expensive
+ // operation that should not be executed too often.
+ root.setAttribute('height', height);
+ }
+ else
+ {
+ var height = Math.max(c.clientHeight, c.scrollHeight) + border - dy;
+ var canvas = this.view.getCanvas();
+ canvas.style.height = height + 'px';
+ }
+
+ c.scrollTop += border - dy;
+ }
+ }
+ else
+ {
+ dy = y - c.scrollTop;
+
+ if (dy < border)
+ {
+ c.scrollTop -= border - dy;
+ }
+ }
+ }
+ }
+ else if (this.allowAutoPanning && !this.panningHandler.active)
+ {
+ if (this.panningManager == null)
+ {
+ this.panningManager = this.createPanningManager();
+ }
+
+ this.panningManager.panTo(x + this.panDx, y + this.panDy);
+ }
+};
+
+
+/**
+ * Function: createPanningManager
+ *
+ * Creates and returns an <mxPanningManager>.
+ */
+mxGraph.prototype.createPanningManager = function()
+{
+ return new mxPanningManager(this);
+};
+
+/**
+ * Function: getBorderSizes
+ *
+ * Returns the size of the border and padding on all four sides of the
+ * container. The left, top, right and bottom borders are stored in the x, y,
+ * width and height of the returned <mxRectangle>, respectively.
+ */
+mxGraph.prototype.getBorderSizes = function()
+{
+ // Helper function to handle string values for border widths (approx)
+ function parseBorder(value)
+ {
+ var result = 0;
+
+ if (value == 'thin')
+ {
+ result = 2;
+ }
+ else if (value == 'medium')
+ {
+ result = 4;
+ }
+ else if (value == 'thick')
+ {
+ result = 6;
+ }
+ else
+ {
+ result = parseInt(value);
+ }
+
+ if (isNaN(result))
+ {
+ result = 0;
+ }
+
+ return result;
+ }
+
+ var style = mxUtils.getCurrentStyle(this.container);
+ var result = new mxRectangle();
+ result.x = parseBorder(style.borderLeftWidth) + parseInt(style.paddingLeft || 0);
+ result.y = parseBorder(style.borderTopWidth) + parseInt(style.paddingTop || 0);
+ result.width = parseBorder(style.borderRightWidth) + parseInt(style.paddingRight || 0);
+ result.height = parseBorder(style.borderBottomWidth) + parseInt(style.paddingBottom || 0);
+
+ return result;
+};
+
+
+/**
+ * Function: getPreferredPageSize
+ *
+ * Returns the preferred size of the background page if <preferPageSize> is true.
+ */
+mxGraph.prototype.getPreferredPageSize = function(bounds, width, height)
+{
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+ var fmt = this.pageFormat;
+ var ps = scale * this.pageScale;
+ var page = new mxRectangle(0, 0, fmt.width * ps, fmt.height * ps);
+
+ var hCount = (this.pageBreaksVisible) ? Math.ceil(width / page.width) : 1;
+ var vCount = (this.pageBreaksVisible) ? Math.ceil(height / page.height) : 1;
+
+ return new mxRectangle(0, 0, hCount * page.width + 2 + tr.x / scale, vCount * page.height + 2 + tr.y / scale);
+};
+
+/**
+ * Function: sizeDidChange
+ *
+ * Called when the size of the graph has changed. This implementation fires
+ * a <size> event after updating the clipping region of the SVG element in
+ * SVG-bases browsers.
+ */
+mxGraph.prototype.sizeDidChange = function()
+{
+ var bounds = this.getGraphBounds();
+
+ if (this.container != null)
+ {
+ var border = this.getBorder();
+
+ var width = Math.max(0, bounds.x + bounds.width + 1 + border);
+ var height = Math.max(0, bounds.y + bounds.height + 1 + border);
+
+ if (this.minimumContainerSize != null)
+ {
+ width = Math.max(width, this.minimumContainerSize.width);
+ height = Math.max(height, this.minimumContainerSize.height);
+ }
+
+ if (this.resizeContainer)
+ {
+ this.doResizeContainer(width, height);
+ }
+
+ if (this.preferPageSize || (!mxClient.IS_IE && this.pageVisible))
+ {
+ var size = this.getPreferredPageSize(bounds, width, height);
+
+ if (size != null)
+ {
+ width = size.width;
+ height = size.height;
+ }
+ }
+
+ if (this.minimumGraphSize != null)
+ {
+ width = Math.max(width, this.minimumGraphSize.width * this.view.scale);
+ height = Math.max(height, this.minimumGraphSize.height * this.view.scale);
+ }
+
+ width = Math.ceil(width - 1);
+ height = Math.ceil(height - 1);
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var root = this.view.getDrawPane().ownerSVGElement;
+
+ root.style.minWidth = Math.max(1, width) + 'px';
+ root.style.minHeight = Math.max(1, height) + 'px';
+ }
+ else
+ {
+ if (mxClient.IS_QUIRKS)
+ {
+ // Quirks mode has no minWidth/minHeight support
+ this.view.updateHtmlCanvasSize(Math.max(1, width), Math.max(1, height));
+ }
+ else
+ {
+ this.view.canvas.style.minWidth = Math.max(1, width) + 'px';
+ this.view.canvas.style.minHeight = Math.max(1, height) + 'px';
+ }
+ }
+
+ this.updatePageBreaks(this.pageBreaksVisible, width - 1, height - 1);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds));
+};
+
+/**
+ * Function: doResizeContainer
+ *
+ * Resizes the container for the given graph width and height.
+ */
+mxGraph.prototype.doResizeContainer = function(width, height)
+{
+ // Fixes container size for different box models
+ if (mxClient.IS_IE)
+ {
+ if (mxClient.IS_QUIRKS)
+ {
+ var borders = this.getBorderSizes();
+
+ // max(2, ...) required for native IE8 in quirks mode
+ width += Math.max(2, borders.x + borders.width + 1);
+ height += Math.max(2, borders.y + borders.height + 1);
+ }
+ else if (document.documentMode >= 9)
+ {
+ width += 3;
+ height += 5;
+ }
+ else
+ {
+ width += 1;
+ height += 1;
+ }
+ }
+ else
+ {
+ height += 1;
+ }
+
+ if (this.maximumContainerSize != null)
+ {
+ width = Math.min(this.maximumContainerSize.width, width);
+ height = Math.min(this.maximumContainerSize.height, height);
+ }
+
+ this.container.style.width = Math.ceil(width) + 'px';
+ this.container.style.height = Math.ceil(height) + 'px';
+};
+
+/**
+ * Function: redrawPageBreaks
+ *
+ * Invokes from <sizeDidChange> to redraw the page breaks.
+ *
+ * Parameters:
+ *
+ * visible - Boolean that specifies if page breaks should be shown.
+ * width - Specifies the width of the container in pixels.
+ * height - Specifies the height of the container in pixels.
+ */
+mxGraph.prototype.updatePageBreaks = function(visible, width, height)
+{
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+ var fmt = this.pageFormat;
+ var ps = scale * this.pageScale;
+ var bounds = new mxRectangle(scale * tr.x, scale * tr.y,
+ fmt.width * ps, fmt.height * ps);
+
+ // Does not show page breaks if the scale is too small
+ visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
+
+ // Draws page breaks independent of translate. To ignore
+ // the translate set bounds.x/y = 0. Note that modulo
+ // in JavaScript has a bug, so use mxUtils instead.
+ bounds.x = mxUtils.mod(bounds.x, bounds.width);
+ bounds.y = mxUtils.mod(bounds.y, bounds.height);
+
+ var horizontalCount = (visible) ? Math.ceil((width - bounds.x) / bounds.width) : 0;
+ var verticalCount = (visible) ? Math.ceil((height - bounds.y) / bounds.height) : 0;
+ var right = width;
+ var bottom = height;
+
+ if (this.horizontalPageBreaks == null && horizontalCount > 0)
+ {
+ this.horizontalPageBreaks = [];
+ }
+
+ if (this.horizontalPageBreaks != null)
+ {
+ for (var i = 0; i <= horizontalCount; i++)
+ {
+ var pts = [new mxPoint(bounds.x + i * bounds.width, 1),
+ new mxPoint(bounds.x + i * bounds.width, bottom)];
+
+ if (this.horizontalPageBreaks[i] != null)
+ {
+ this.horizontalPageBreaks[i].scale = 1;
+ this.horizontalPageBreaks[i].points = pts;
+ this.horizontalPageBreaks[i].redraw();
+ }
+ else
+ {
+ var pageBreak = new mxPolyline(pts, this.pageBreakColor, this.scale);
+ pageBreak.dialect = this.dialect;
+ pageBreak.isDashed = this.pageBreakDashed;
+ pageBreak.scale = scale;
+ pageBreak.crisp = true;
+ pageBreak.init(this.view.backgroundPane);
+ pageBreak.redraw();
+
+ this.horizontalPageBreaks[i] = pageBreak;
+ }
+ }
+
+ for (var i = horizontalCount; i < this.horizontalPageBreaks.length; i++)
+ {
+ this.horizontalPageBreaks[i].destroy();
+ }
+
+ this.horizontalPageBreaks.splice(horizontalCount, this.horizontalPageBreaks.length - horizontalCount);
+ }
+
+ if (this.verticalPageBreaks == null && verticalCount > 0)
+ {
+ this.verticalPageBreaks = [];
+ }
+
+ if (this.verticalPageBreaks != null)
+ {
+ for (var i = 0; i <= verticalCount; i++)
+ {
+ var pts = [new mxPoint(1, bounds.y + i * bounds.height),
+ new mxPoint(right, bounds.y + i * bounds.height)];
+
+ if (this.verticalPageBreaks[i] != null)
+ {
+ this.verticalPageBreaks[i].scale = 1;
+ this.verticalPageBreaks[i].points = pts;
+ this.verticalPageBreaks[i].redraw();
+ }
+ else
+ {
+ var pageBreak = new mxPolyline(pts, this.pageBreakColor, scale);
+ pageBreak.dialect = this.dialect;
+ pageBreak.isDashed = this.pageBreakDashed;
+ pageBreak.scale = scale;
+ pageBreak.crisp = true;
+ pageBreak.init(this.view.backgroundPane);
+ pageBreak.redraw();
+
+ this.verticalPageBreaks[i] = pageBreak;
+ }
+ }
+
+ for (var i = verticalCount; i < this.verticalPageBreaks.length; i++)
+ {
+ this.verticalPageBreaks[i].destroy();
+ }
+
+ this.verticalPageBreaks.splice(verticalCount, this.verticalPageBreaks.length - verticalCount);
+ }
+};
+
+/**
+ * Group: Cell styles
+ */
+
+/**
+ * Function: getCellStyle
+ *
+ * Returns an array of key, value pairs representing the cell style for the
+ * given cell. If no string is defined in the model that specifies the
+ * style, then the default style for the cell is returned or <EMPTY_ARRAY>,
+ * if not style can be found. Note: You should try and get the cell state
+ * for the given cell and use the cached style in the state before using
+ * this method.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose style should be returned as an array.
+ */
+mxGraph.prototype.getCellStyle = function(cell)
+{
+ var stylename = this.model.getStyle(cell);
+ var style = null;
+
+ // Gets the default style for the cell
+ if (this.model.isEdge(cell))
+ {
+ style = this.stylesheet.getDefaultEdgeStyle();
+ }
+ else
+ {
+ style = this.stylesheet.getDefaultVertexStyle();
+ }
+
+ // Resolves the stylename using the above as the default
+ if (stylename != null)
+ {
+ style = this.postProcessCellStyle(this.stylesheet.getCellStyle(stylename, style));
+ }
+
+ // Returns a non-null value if no style can be found
+ if (style == null)
+ {
+ style = mxGraph.prototype.EMPTY_ARRAY;
+ }
+
+ return style;
+};
+
+/**
+ * Function: postProcessCellStyle
+ *
+ * Tries to resolve the value for the image style in the image bundles and
+ * turns short data URIs as defined in mxImageBundle to data URIs as
+ * defined in RFC 2397 of the IETF.
+ */
+mxGraph.prototype.postProcessCellStyle = function(style)
+{
+ if (style != null)
+ {
+ var key = style[mxConstants.STYLE_IMAGE];
+ var image = this.getImageFromBundles(key);
+
+ if (image != null)
+ {
+ style[mxConstants.STYLE_IMAGE] = image;
+ }
+ else
+ {
+ image = key;
+ }
+
+ // Converts short data uris to normal data uris
+ if (image != null && image.substring(0, 11) == "data:image/")
+ {
+ var comma = image.indexOf(',');
+
+ if (comma > 0)
+ {
+ image = image.substring(0, comma) + ";base64,"
+ + image.substring(comma + 1);
+ }
+
+ style[mxConstants.STYLE_IMAGE] = image;
+ }
+ }
+
+ return style;
+};
+
+/**
+ * Function: setCellStyle
+ *
+ * Sets the style of the specified cells. If no cells are given, then the
+ * selection cells are changed.
+ *
+ * Parameters:
+ *
+ * style - String representing the new style of the cells.
+ * cells - Optional array of <mxCells> to set the style for. Default is the
+ * selection cells.
+ */
+mxGraph.prototype.setCellStyle = function(style, cells)
+{
+ cells = cells || this.getSelectionCells();
+
+ if (cells != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ this.model.setStyle(cells[i], style);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: toggleCellStyle
+ *
+ * Toggles the boolean value for the given key in the style of the
+ * given cell. If no cell is specified then the selection cell is
+ * used.
+ *
+ * Parameter:
+ *
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cell - Optional <mxCell> whose style should be modified. Default is
+ * the selection cell.
+ */
+mxGraph.prototype.toggleCellStyle = function(key, defaultValue, cell)
+{
+ cell = cell || this.getSelectionCell();
+
+ this.toggleCellStyles(key, defaultValue, [cell]);
+};
+
+/**
+ * Function: toggleCellStyles
+ *
+ * Toggles the boolean value for the given key in the style of the given
+ * cells. If no cells are specified, then the selection cells are used. For
+ * example, this can be used to toggle <mxConstants.STYLE_ROUNDED> or any
+ * other style with a boolean value.
+ *
+ * Parameter:
+ *
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cells - Optional array of <mxCells> whose styles should be modified.
+ * Default is the selection cells.
+ */
+mxGraph.prototype.toggleCellStyles = function(key, defaultValue, cells)
+{
+ defaultValue = (defaultValue != null) ? defaultValue : false;
+ cells = cells || this.getSelectionCells();
+
+ if (cells != null && cells.length > 0)
+ {
+ var state = this.view.getState(cells[0]);
+ var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+
+ if (style != null)
+ {
+ var val = (mxUtils.getValue(style, key, defaultValue)) ? 0 : 1;
+ this.setCellStyles(key, val, cells);
+ }
+ }
+};
+
+/**
+ * Function: setCellStyles
+ *
+ * Sets the key to value in the styles of the given cells. This will modify
+ * the existing cell styles in-place and override any existing assignment
+ * for the given key. If no cells are specified, then the selection cells
+ * are changed. If no value is specified, then the respective key is
+ * removed from the styles.
+ *
+ * Parameters:
+ *
+ * key - String representing the key to be assigned.
+ * value - String representing the new value for the key.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyles = function(key, value, cells)
+{
+ cells = cells || this.getSelectionCells();
+ mxUtils.setCellStyles(this.model, cells, key, value);
+};
+
+/**
+ * Function: toggleCellStyleFlags
+ *
+ * Toggles the given bit for the given key in the styles of the specified
+ * cells.
+ *
+ * Parameters:
+ *
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.toggleCellStyleFlags = function(key, flag, cells)
+{
+ this.setCellStyleFlags(key, flag, null, cells);
+};
+
+/**
+ * Function: setCellStyleFlags
+ *
+ * Sets or toggles the given bit for the given key in the styles of the
+ * specified cells.
+ *
+ * Parameters:
+ *
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * value - Boolean value to be used or null if the value should be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyleFlags = function(key, flag, value, cells)
+{
+ cells = cells || this.getSelectionCells();
+
+ if (cells != null && cells.length > 0)
+ {
+ if (value == null)
+ {
+ var state = this.view.getState(cells[0]);
+ var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+
+ if (style != null)
+ {
+ var current = parseInt(style[key] || 0);
+ value = !((current & flag) == flag);
+ }
+ }
+
+ mxUtils.setCellStyleFlags(this.model, cells, key, flag, value);
+ }
+};
+
+/**
+ * Group: Cell alignment and orientation
+ */
+
+/**
+ * Function: alignCells
+ *
+ * Aligns the given cells vertically or horizontally according to the given
+ * alignment using the optional parameter as the coordinate.
+ *
+ * Parameters:
+ *
+ * align - Specifies the alignment. Possible values are all constants in
+ * mxConstants with an ALIGN prefix.
+ * cells - Array of <mxCells> to be aligned.
+ * param - Optional coordinate for the alignment.
+ */
+mxGraph.prototype.alignCells = function(align, cells, param)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ if (cells != null && cells.length > 1)
+ {
+ // Finds the required coordinate for the alignment
+ if (param == null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null && !this.model.isEdge(cells[i]))
+ {
+ if (param == null)
+ {
+ if (align == mxConstants.ALIGN_CENTER)
+ {
+ param = geo.x + geo.width / 2;
+ break;
+ }
+ else if (align == mxConstants.ALIGN_RIGHT)
+ {
+ param = geo.x + geo.width;
+ }
+ else if (align == mxConstants.ALIGN_TOP)
+ {
+ param = geo.y;
+ }
+ else if (align == mxConstants.ALIGN_MIDDLE)
+ {
+ param = geo.y + geo.height / 2;
+ break;
+ }
+ else if (align == mxConstants.ALIGN_BOTTOM)
+ {
+ param = geo.y + geo.height;
+ }
+ else
+ {
+ param = geo.x;
+ }
+ }
+ else
+ {
+ if (align == mxConstants.ALIGN_RIGHT)
+ {
+ param = Math.max(param, geo.x + geo.width);
+ }
+ else if (align == mxConstants.ALIGN_TOP)
+ {
+ param = Math.min(param, geo.y);
+ }
+ else if (align == mxConstants.ALIGN_BOTTOM)
+ {
+ param = Math.max(param, geo.y + geo.height);
+ }
+ else
+ {
+ param = Math.min(param, geo.x);
+ }
+ }
+ }
+ }
+ }
+
+ // Aligns the cells to the coordinate
+ if (param != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null && !this.model.isEdge(cells[i]))
+ {
+ geo = geo.clone();
+
+ if (align == mxConstants.ALIGN_CENTER)
+ {
+ geo.x = param - geo.width / 2;
+ }
+ else if (align == mxConstants.ALIGN_RIGHT)
+ {
+ geo.x = param - geo.width;
+ }
+ else if (align == mxConstants.ALIGN_TOP)
+ {
+ geo.y = param;
+ }
+ else if (align == mxConstants.ALIGN_MIDDLE)
+ {
+ geo.y = param - geo.height / 2;
+ }
+ else if (align == mxConstants.ALIGN_BOTTOM)
+ {
+ geo.y = param - geo.height;
+ }
+ else
+ {
+ geo.x = param;
+ }
+
+ this.model.setGeometry(cells[i], geo);
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS,
+ 'align', align, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+ }
+
+ return cells;
+};
+
+/**
+ * Function: flipEdge
+ *
+ * Toggles the style of the given edge between null (or empty) and
+ * <alternateEdgeStyle>. This method fires <mxEvent.FLIP_EDGE> while the
+ * transaction is in progress. Returns the edge that was flipped.
+ *
+ * Here is an example that overrides this implementation to invert the
+ * value of <mxConstants.STYLE_ELBOW> without removing any existing styles.
+ *
+ * (code)
+ * graph.flipEdge = function(edge)
+ * {
+ * if (edge != null)
+ * {
+ * var state = this.view.getState(edge);
+ * var style = (state != null) ? state.style : this.getCellStyle(edge);
+ *
+ * if (style != null)
+ * {
+ * var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
+ * mxConstants.ELBOW_HORIZONTAL);
+ * var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
+ * mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
+ * this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
+ * }
+ * }
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose style should be changed.
+ */
+mxGraph.prototype.flipEdge = function(edge)
+{
+ if (edge != null &&
+ this.alternateEdgeStyle != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var style = this.model.getStyle(edge);
+
+ if (style == null || style.length == 0)
+ {
+ this.model.setStyle(edge, this.alternateEdgeStyle);
+ }
+ else
+ {
+ this.model.setStyle(edge, null);
+ }
+
+ // Removes all existing control points
+ this.resetEdge(edge);
+ this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, 'edge', edge));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return edge;
+};
+
+/**
+ * Function: addImageBundle
+ *
+ * Adds the specified <mxImageBundle>.
+ */
+mxGraph.prototype.addImageBundle = function(bundle)
+{
+ this.imageBundles.push(bundle);
+};
+
+/**
+ * Function: removeImageBundle
+ *
+ * Removes the specified <mxImageBundle>.
+ */
+mxGraph.prototype.removeImageBundle = function(bundle)
+{
+ var tmp = [];
+
+ for (var i = 0; i < this.imageBundles.length; i++)
+ {
+ if (this.imageBundles[i] != bundle)
+ {
+ tmp.push(this.imageBundles[i]);
+ }
+ }
+
+ this.imageBundles = tmp;
+};
+
+/**
+ * Function: getImageFromBundles
+ *
+ * Searches all <imageBundles> for the specified key and returns the value
+ * for the first match or null if the key is not found.
+ */
+mxGraph.prototype.getImageFromBundles = function(key)
+{
+ if (key != null)
+ {
+ for (var i = 0; i < this.imageBundles.length; i++)
+ {
+ var image = this.imageBundles[i].getImage(key);
+
+ if (image != null)
+ {
+ return image;
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Group: Order
+ */
+
+/**
+ * Function: orderCells
+ *
+ * Moves the given cells to the front or back. The change is carried out
+ * using <cellsOrdered>. This method fires <mxEvent.ORDER_CELLS> while the
+ * transaction is in progress.
+ *
+ * Parameters:
+ *
+ * back - Boolean that specifies if the cells should be moved to back.
+ * cells - Array of <mxCells> to move to the background. If null is
+ * specified then the selection cells are used.
+ */
+ mxGraph.prototype.orderCells = function(back, cells)
+ {
+ if (cells == null)
+ {
+ cells = mxUtils.sortCells(this.getSelectionCells(), true);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsOrdered(cells, back);
+ this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS,
+ 'back', back, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+ };
+
+/**
+ * Function: cellsOrdered
+ *
+ * Moves the given cells to the front or back. This method fires
+ * <mxEvent.CELLS_ORDERED> while the transaction is in progress.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose order should be changed.
+ * back - Boolean that specifies if the cells should be moved to back.
+ */
+ mxGraph.prototype.cellsOrdered = function(cells, back)
+ {
+ if (cells != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var parent = this.model.getParent(cells[i]);
+
+ if (back)
+ {
+ this.model.add(parent, cells[i], i);
+ }
+ else
+ {
+ this.model.add(parent, cells[i],
+ this.model.getChildCount(parent) - 1);
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED,
+ 'back', back, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Group: Grouping
+ */
+
+/**
+ * Function: groupCells
+ *
+ * Adds the cells into the given group. The change is carried out using
+ * <cellsAdded>, <cellsMoved> and <cellsResized>. This method fires
+ * <mxEvent.GROUP_CELLS> while the transaction is in progress. Returns the
+ * new group. A group is only created if there is at least one entry in the
+ * given array of cells.
+ *
+ * Parameters:
+ *
+ * group - <mxCell> that represents the target group. If null is specified
+ * then a new group is created using <createGroupCell>.
+ * border - Optional integer that specifies the border between the child
+ * area and the group bounds. Default is 0.
+ * cells - Optional array of <mxCells> to be grouped. If null is specified
+ * then the selection cells are used.
+ */
+mxGraph.prototype.groupCells = function(group, border, cells)
+{
+ if (cells == null)
+ {
+ cells = mxUtils.sortCells(this.getSelectionCells(), true);
+ }
+
+ cells = this.getCellsForGroup(cells);
+
+ if (group == null)
+ {
+ group = this.createGroupCell(cells);
+ }
+
+ var bounds = this.getBoundsForGroup(group, cells, border);
+
+ if (cells.length > 0 && bounds != null)
+ {
+ // Uses parent of group or previous parent of first child
+ var parent = this.model.getParent(group);
+
+ if (parent == null)
+ {
+ parent = this.model.getParent(cells[0]);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ // Checks if the group has a geometry and
+ // creates one if one does not exist
+ if (this.getCellGeometry(group) == null)
+ {
+ this.model.setGeometry(group, new mxGeometry());
+ }
+
+ // Adds the children into the group and moves
+ var index = this.model.getChildCount(group);
+ this.cellsAdded(cells, group, index, null, null, false, false);
+ this.cellsMoved(cells, -bounds.x, -bounds.y, false, true);
+
+ // Adds the group into the parent and resizes
+ index = this.model.getChildCount(parent);
+ this.cellsAdded([group], parent, index, null, null, false);
+ this.cellsResized([group], [bounds]);
+
+ this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS,
+ 'group', group, 'border', border, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return group;
+};
+
+/**
+ * Function: getCellsForGroup
+ *
+ * Returns the cells with the same parent as the first cell
+ * in the given array.
+ */
+mxGraph.prototype.getCellsForGroup = function(cells)
+{
+ var result = [];
+
+ if (cells != null && cells.length > 0)
+ {
+ var parent = this.model.getParent(cells[0]);
+ result.push(cells[0]);
+
+ // Filters selection cells with the same parent
+ for (var i = 1; i < cells.length; i++)
+ {
+ if (this.model.getParent(cells[i]) == parent)
+ {
+ result.push(cells[i]);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getBoundsForGroup
+ *
+ * Returns the bounds to be used for the given group and children.
+ */
+mxGraph.prototype.getBoundsForGroup = function(group, children, border)
+{
+ var result = this.getBoundingBoxFromGeometry(children);
+
+ if (result != null)
+ {
+ if (this.isSwimlane(group))
+ {
+ var size = this.getStartSize(group);
+
+ result.x -= size.width;
+ result.y -= size.height;
+ result.width += size.width;
+ result.height += size.height;
+ }
+
+ // Adds the border
+ result.x -= border;
+ result.y -= border;
+ result.width += 2 * border;
+ result.height += 2 * border;
+ }
+
+ return result;
+};
+
+/**
+ * Function: createGroupCell
+ *
+ * Hook for creating the group cell to hold the given array of <mxCells> if
+ * no group cell was given to the <group> function.
+ *
+ * The following code can be used to set the style of new group cells.
+ *
+ * (code)
+ * var graphCreateGroupCell = graph.createGroupCell;
+ * graph.createGroupCell = function(cells)
+ * {
+ * var group = graphCreateGroupCell.apply(this, arguments);
+ * group.setStyle('group');
+ *
+ * return group;
+ * };
+ */
+mxGraph.prototype.createGroupCell = function(cells)
+{
+ var group = new mxCell('');
+ group.setVertex(true);
+ group.setConnectable(false);
+
+ return group;
+};
+
+/**
+ * Function: ungroupCells
+ *
+ * Ungroups the given cells by moving the children the children to their
+ * parents parent and removing the empty groups. Returns the children that
+ * have been removed from the groups.
+ *
+ * Parameters:
+ *
+ * cells - Array of cells to be ungrouped. If null is specified then the
+ * selection cells are used.
+ */
+mxGraph.prototype.ungroupCells = function(cells)
+{
+ var result = [];
+
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+
+ // Finds the cells with children
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.model.getChildCount(cells[i]) > 0)
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ cells = tmp;
+ }
+
+ if (cells != null && cells.length > 0)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var children = this.model.getChildren(cells[i]);
+
+ if (children != null && children.length > 0)
+ {
+ children = children.slice();
+ var parent = this.model.getParent(cells[i]);
+ var index = this.model.getChildCount(parent);
+
+ this.cellsAdded(children, parent, index, null, null, true);
+ result = result.concat(children);
+ }
+ }
+
+ this.cellsRemoved(this.addAllEdges(cells));
+ this.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS,
+ 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: removeCellsFromParent
+ *
+ * Removes the specified cells from their parents and adds them to the
+ * default parent. Returns the cells that were removed from their parents.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be removed from their parents.
+ */
+mxGraph.prototype.removeCellsFromParent = function(cells)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ var parent = this.getDefaultParent();
+ var index = this.model.getChildCount(parent);
+
+ this.cellsAdded(cells, parent, index, null, null, true);
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT,
+ 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: updateGroupBounds
+ *
+ * Updates the bounds of the given array of groups so that it includes
+ * all child vertices.
+ *
+ * Parameters:
+ *
+ * cells - The groups whose bounds should be updated.
+ * border - Optional border to be added in the group. Default is 0.
+ * moveGroup - Optional boolean that allows the group to be moved. Default
+ * is false.
+ */
+mxGraph.prototype.updateGroupBounds = function(cells, border, moveGroup)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ border = (border != null) ? border : 0;
+ moveGroup = (moveGroup != null) ? moveGroup : false;
+
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var children = this.getChildCells(cells[i]);
+
+ if (children != null && children.length > 0)
+ {
+ var childBounds = this.getBoundingBoxFromGeometry(children);
+
+ if (childBounds.width > 0 && childBounds.height > 0)
+ {
+ var size = (this.isSwimlane(cells[i])) ?
+ this.getStartSize(cells[i]) : new mxRectangle();
+
+ geo = geo.clone();
+
+ if (moveGroup)
+ {
+ geo.x += childBounds.x - size.width - border;
+ geo.y += childBounds.y - size.height - border;
+ }
+
+ geo.width = childBounds.width + size.width + 2 * border;
+ geo.height = childBounds.height + size.height + 2 * border;
+
+ this.model.setGeometry(cells[i], geo);
+ this.moveCells(children, -childBounds.x + size.width + border,
+ -childBounds.y + size.height + border);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Group: Cell cloning, insertion and removal
+ */
+
+/**
+ * Function: cloneCells
+ *
+ * Returns the clones for the given cells. If the terminal of an edge is
+ * not in the given array, then the respective end is assigned a terminal
+ * point and the terminal is removed.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be cloned.
+ * allowInvalidEdges - Optional boolean that specifies if invalid edges
+ * should be cloned. Default is true.
+ */
+mxGraph.prototype.cloneCells = function(cells, allowInvalidEdges)
+{
+ allowInvalidEdges = (allowInvalidEdges != null) ? allowInvalidEdges : true;
+ var clones = null;
+
+ if (cells != null)
+ {
+ // Creates a hashtable for cell lookups
+ var hash = new Object();
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ tmp.push(cells[i]);
+ }
+
+ if (tmp.length > 0)
+ {
+ var scale = this.view.scale;
+ var trans = this.view.translate;
+ clones = this.model.cloneCells(cells, true);
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!allowInvalidEdges && this.model.isEdge(clones[i]) &&
+ this.getEdgeValidationError(clones[i],
+ this.model.getTerminal(clones[i], true),
+ this.model.getTerminal(clones[i], false)) != null)
+ {
+ clones[i] = null;
+ }
+ else
+ {
+ var g = this.model.getGeometry(clones[i]);
+
+ if (g != null)
+ {
+ var state = this.view.getState(cells[i]);
+ var pstate = this.view.getState(
+ this.model.getParent(cells[i]));
+
+ if (state != null && pstate != null)
+ {
+ var dx = pstate.origin.x;
+ var dy = pstate.origin.y;
+
+ if (this.model.isEdge(clones[i]))
+ {
+ var pts = state.absolutePoints;
+
+ // Checks if the source is cloned or sets the terminal point
+ var src = this.model.getTerminal(cells[i], true);
+ var srcId = mxCellPath.create(src);
+
+ while (src != null && hash[srcId] == null)
+ {
+ src = this.model.getParent(src);
+ srcId = mxCellPath.create(src);
+ }
+
+ if (src == null)
+ {
+ g.setTerminalPoint(
+ new mxPoint(pts[0].x / scale - trans.x,
+ pts[0].y / scale - trans.y), true);
+ }
+
+ // Checks if the target is cloned or sets the terminal point
+ var trg = this.model.getTerminal(cells[i], false);
+ var trgId = mxCellPath.create(trg);
+
+ while (trg != null && hash[trgId] == null)
+ {
+ trg = this.model.getParent(trg);
+ trgId = mxCellPath.create(trg);
+ }
+
+ if (trg == null)
+ {
+ var n = pts.length - 1;
+ g.setTerminalPoint(
+ new mxPoint(pts[n].x / scale - trans.x,
+ pts[n].y / scale - trans.y), false);
+ }
+
+ // Translates the control points
+ var points = g.points;
+
+ if (points != null)
+ {
+ for (var j = 0; j < points.length; j++)
+ {
+ points[j].x += dx;
+ points[j].y += dy;
+ }
+ }
+ }
+ else
+ {
+ g.x += dx;
+ g.y += dy;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ clones = [];
+ }
+ }
+
+ return clones;
+};
+
+/**
+ * Function: insertVertex
+ *
+ * Adds a new vertex into the given parent <mxCell> using value as the user
+ * object and the given coordinates as the <mxGeometry> of the new vertex.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * When adding new vertices from a mouse event, one should take into
+ * account the offset of the graph container and the scale and translation
+ * of the view in order to find the correct unscaled, untranslated
+ * coordinates using <mxGraph.getPointForEvent> as follows:
+ *
+ * (code)
+ * var pt = graph.getPointForEvent(evt);
+ * var parent = graph.getDefaultParent();
+ * graph.insertVertex(parent, null,
+ * 'Hello, World!', x, y, 220, 30);
+ * (end)
+ *
+ * For adding image cells, the style parameter can be assigned as
+ *
+ * (code)
+ * stylename;image=imageUrl
+ * (end)
+ *
+ * See <mxGraph> for more information on using images.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that specifies the parent of the new vertex.
+ * id - Optional string that defines the Id of the new vertex.
+ * value - Object to be used as the user object.
+ * x - Integer that defines the x coordinate of the vertex.
+ * y - Integer that defines the y coordinate of the vertex.
+ * width - Integer that defines the width of the vertex.
+ * height - Integer that defines the height of the vertex.
+ * style - Optional string that defines the cell style.
+ * relative - Optional boolean that specifies if the geometry is relative.
+ * Default is false.
+ */
+mxGraph.prototype.insertVertex = function(parent, id, value,
+ x, y, width, height, style, relative)
+{
+ var vertex = this.createVertex(parent, id, value, x, y, width, height, style, relative);
+
+ return this.addCell(vertex, parent);
+};
+
+/**
+ * Function: createVertex
+ *
+ * Hook method that creates the new vertex for <insertVertex>.
+ */
+mxGraph.prototype.createVertex = function(parent, id, value,
+ x, y, width, height, style, relative)
+{
+ // Creates the geometry for the vertex
+ var geometry = new mxGeometry(x, y, width, height);
+ geometry.relative = (relative != null) ? relative : false;
+
+ // Creates the vertex
+ var vertex = new mxCell(value, geometry, style);
+ vertex.setId(id);
+ vertex.setVertex(true);
+ vertex.setConnectable(true);
+
+ return vertex;
+};
+
+/**
+ * Function: insertEdge
+ *
+ * Adds a new edge into the given parent <mxCell> using value as the user
+ * object and the given source and target as the terminals of the new edge.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that specifies the parent of the new edge.
+ * id - Optional string that defines the Id of the new edge.
+ * value - JavaScript object to be used as the user object.
+ * source - <mxCell> that defines the source of the edge.
+ * target - <mxCell> that defines the target of the edge.
+ * style - Optional string that defines the cell style.
+ */
+mxGraph.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+ var edge = this.createEdge(parent, id, value, source, target, style);
+
+ return this.addEdge(edge, parent, source, target);
+};
+
+/**
+ * Function: createEdge
+ *
+ * Hook method that creates the new edge for <insertEdge>. This
+ * implementation does not set the source and target of the edge, these
+ * are set when the edge is added to the model.
+ *
+ */
+mxGraph.prototype.createEdge = function(parent, id, value, source, target, style)
+{
+ // Creates the edge
+ var edge = new mxCell(value, new mxGeometry(), style);
+ edge.setId(id);
+ edge.setEdge(true);
+ edge.geometry.relative = true;
+
+ return edge;
+};
+
+/**
+ * Function: addEdge
+ *
+ * Adds the edge to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the edge that was
+ * added.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ * index - Optional index to insert the cells at. Default is to append.
+ */
+mxGraph.prototype.addEdge = function(edge, parent, source, target, index)
+{
+ return this.addCell(edge, parent, index, source, target);
+};
+
+/**
+ * Function: addCell
+ *
+ * Adds the cell to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the cell that was
+ * added.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.addCell = function(cell, parent, index, source, target)
+{
+ return this.addCells([cell], parent, index, source, target)[0];
+};
+
+/**
+ * Function: addCells
+ *
+ * Adds the cells to the parent at the given index, connecting each cell to
+ * the optional source and target terminal. The change is carried out using
+ * <cellsAdded>. This method fires <mxEvent.ADD_CELLS> while the
+ * transaction is in progress. Returns the cells that were added.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be inserted.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional source <mxCell> for all inserted cells.
+ * target - Optional target <mxCell> for all inserted cells.
+ */
+mxGraph.prototype.addCells = function(cells, parent, index, source, target)
+{
+ if (parent == null)
+ {
+ parent = this.getDefaultParent();
+ }
+
+ if (index == null)
+ {
+ index = this.model.getChildCount(parent);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsAdded(cells, parent, index, source, target, false, true);
+ this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS, 'cells', cells,
+ 'parent', parent, 'index', index, 'source', source, 'target', target));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsAdded
+ *
+ * Adds the specified cells to the given parent. This method fires
+ * <mxEvent.CELLS_ADDED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsAdded = function(cells, parent, index, source, target, absolute, constrain)
+{
+ if (cells != null && parent != null && index != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var parentState = (absolute) ? this.view.getState(parent) : null;
+ var o1 = (parentState != null) ? parentState.origin : null;
+ var zero = new mxPoint(0, 0);
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] == null)
+ {
+ index--;
+ }
+ else
+ {
+ var previous = this.model.getParent(cells[i]);
+
+ // Keeps the cell at its absolute location
+ if (o1 != null && cells[i] != parent && parent != previous)
+ {
+ var oldState = this.view.getState(previous);
+ var o2 = (oldState != null) ? oldState.origin : zero;
+ var geo = this.model.getGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var dx = o2.x - o1.x;
+ var dy = o2.y - o1.y;
+
+ // FIXME: Cells should always be inserted first before any other edit
+ // to avoid forward references in sessions.
+ geo = geo.clone();
+ geo.translate(dx, dy);
+
+ if (!geo.relative && this.model.isVertex(cells[i]) &&
+ !this.isAllowNegativeCoordinates())
+ {
+ geo.x = Math.max(0, geo.x);
+ geo.y = Math.max(0, geo.y);
+ }
+
+ this.model.setGeometry(cells[i], geo);
+ }
+ }
+
+ // Decrements all following indices
+ // if cell is already in parent
+ if (parent == previous)
+ {
+ index--;
+ }
+
+ this.model.add(parent, cells[i], index + i);
+
+ // Extends the parent
+ if (this.isExtendParentsOnAdd() && this.isExtendParent(cells[i]))
+ {
+ this.extendParent(cells[i]);
+ }
+
+ // Constrains the child
+ if (constrain == null || constrain)
+ {
+ this.constrainChild(cells[i]);
+ }
+
+ // Sets the source terminal
+ if (source != null)
+ {
+ this.cellConnected(cells[i], source, true);
+ }
+
+ // Sets the target terminal
+ if (target != null)
+ {
+ this.cellConnected(cells[i], target, false);
+ }
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', cells,
+ 'parent', parent, 'index', index, 'source', source, 'target', target,
+ 'absolute', absolute));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: removeCells
+ *
+ * Removes the given cells from the graph including all connected edges if
+ * includeEdges is true. The change is carried out using <cellsRemoved>.
+ * This method fires <mxEvent.REMOVE_CELLS> while the transaction is in
+ * progress. The removed cells are returned as an array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to remove. If null is specified then the
+ * selection cells which are deletable are used.
+ * includeEdges - Optional boolean which specifies if all connected edges
+ * should be removed as well. Default is true.
+ */
+mxGraph.prototype.removeCells = function(cells, includeEdges)
+{
+ includeEdges = (includeEdges != null) ? includeEdges : true;
+
+ if (cells == null)
+ {
+ cells = this.getDeletableCells(this.getSelectionCells());
+ }
+
+ // Adds all edges to the cells
+ if (includeEdges)
+ {
+ cells = this.getDeletableCells(this.addAllEdges(cells));
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsRemoved(cells);
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS,
+ 'cells', cells, 'includeEdges', includeEdges));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsRemoved
+ *
+ * Removes the given cells from the model. This method fires
+ * <mxEvent.CELLS_REMOVED> while the transaction is in progress.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to remove.
+ */
+mxGraph.prototype.cellsRemoved = function(cells)
+{
+ if (cells != null && cells.length > 0)
+ {
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+
+ this.model.beginUpdate();
+ try
+ {
+ // Creates hashtable for faster lookup
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ // Disconnects edges which are not in cells
+ var edges = this.getConnections(cells[i]);
+
+ for (var j = 0; j < edges.length; j++)
+ {
+ var id = mxCellPath.create(edges[j]);
+
+ if (hash[id] == null)
+ {
+ var geo = this.model.getGeometry(edges[j]);
+
+ if (geo != null)
+ {
+ var state = this.view.getState(edges[j]);
+
+ if (state != null)
+ {
+ geo = geo.clone();
+ var source = state.getVisibleTerminal(true) == cells[i];
+ var pts = state.absolutePoints;
+ var n = (source) ? 0 : pts.length - 1;
+
+ geo.setTerminalPoint(
+ new mxPoint(pts[n].x / scale - tr.x,
+ pts[n].y / scale - tr.y), source);
+ this.model.setTerminal(edges[j], null, source);
+ this.model.setGeometry(edges[j], geo);
+ }
+ }
+ }
+ }
+
+ this.model.remove(cells[i]);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED,
+ 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: splitEdge
+ *
+ * Splits the given edge by adding the newEdge between the previous source
+ * and the given cell and reconnecting the source of the given edge to the
+ * given cell. This method fires <mxEvent.SPLIT_EDGE> while the transaction
+ * is in progress. Returns the new edge that was inserted.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that represents the cells to insert into the edge.
+ * newEdge - <mxCell> that represents the edge to be inserted.
+ * dx - Optional integer that specifies the vector to move the cells.
+ * dy - Optional integer that specifies the vector to move the cells.
+ */
+mxGraph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy)
+{
+ dx = dx || 0;
+ dy = dy || 0;
+
+ if (newEdge == null)
+ {
+ newEdge = this.cloneCells([edge])[0];
+ }
+
+ var parent = this.model.getParent(edge);
+ var source = this.model.getTerminal(edge, true);
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsMoved(cells, dx, dy, false, false);
+ this.cellsAdded(cells, parent, this.model.getChildCount(parent), null, null,
+ true);
+ this.cellsAdded([newEdge], parent, this.model.getChildCount(parent),
+ source, cells[0], false);
+ this.cellConnected(edge, cells[0], true);
+ this.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, 'edge', edge,
+ 'cells', cells, 'newEdge', newEdge, 'dx', dx, 'dy', dy));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return newEdge;
+};
+
+/**
+ * Group: Cell visibility
+ */
+
+/**
+ * Function: toggleCells
+ *
+ * Sets the visible state of the specified cells and all connected edges
+ * if includeEdges is true. The change is carried out using <cellsToggled>.
+ * This method fires <mxEvent.TOGGLE_CELLS> while the transaction is in
+ * progress. Returns the cells whose visible state was changed.
+ *
+ * Parameters:
+ *
+ * show - Boolean that specifies the visible state to be assigned.
+ * cells - Array of <mxCells> whose visible state should be changed. If
+ * null is specified then the selection cells are used.
+ * includeEdges - Optional boolean indicating if the visible state of all
+ * connected edges should be changed as well. Default is true.
+ */
+mxGraph.prototype.toggleCells = function(show, cells, includeEdges)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ // Adds all connected edges recursively
+ if (includeEdges)
+ {
+ cells = this.addAllEdges(cells);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsToggled(cells, show);
+ this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS,
+ 'show', show, 'cells', cells, 'includeEdges', includeEdges));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsToggled
+ *
+ * Sets the visible state of the specified cells.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose visible state should be changed.
+ * show - Boolean that specifies the visible state to be assigned.
+ */
+mxGraph.prototype.cellsToggled = function(cells, show)
+{
+ if (cells != null && cells.length > 0)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ this.model.setVisible(cells[i], show);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Group: Folding
+ */
+
+/**
+ * Function: foldCells
+ *
+ * Sets the collapsed state of the specified cells and all descendants
+ * if recurse is true. The change is carried out using <cellsFolded>.
+ * This method fires <mxEvent.FOLD_CELLS> while the transaction is in
+ * progress. Returns the cells whose collapsed state was changed.
+ *
+ * Parameters:
+ *
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Optional boolean indicating if the collapsed state of all
+ * descendants should be set. Default is false.
+ * cells - Array of <mxCells> whose collapsed state should be set. If
+ * null is specified then the foldable selection cells are used.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ */
+mxGraph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable)
+{
+ recurse = (recurse != null) ? recurse : false;
+
+ if (cells == null)
+ {
+ cells = this.getFoldableCells(this.getSelectionCells(), collapse);
+ }
+
+ this.stopEditing(false);
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsFolded(cells, collapse, recurse, checkFoldable);
+ this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS,
+ 'collapse', collapse, 'recurse', recurse, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsFolded
+ *
+ * Sets the collapsed state of the specified cells. This method fires
+ * <mxEvent.CELLS_FOLDED> while the transaction is in progress. Returns the
+ * cells whose collapsed state was changed.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose collapsed state should be set.
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Boolean indicating if the collapsed state of all descendants
+ * should be set.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ */
+mxGraph.prototype.cellsFolded = function(cells, collapse, recurse, checkFoldable)
+{
+ if (cells != null && cells.length > 0)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if ((!checkFoldable || this.isCellFoldable(cells[i], collapse)) &&
+ collapse != this.isCellCollapsed(cells[i]))
+ {
+ this.model.setCollapsed(cells[i], collapse);
+ this.swapBounds(cells[i], collapse);
+
+ if (this.isExtendParent(cells[i]))
+ {
+ this.extendParent(cells[i]);
+ }
+
+ if (recurse)
+ {
+ var children = this.model.getChildren(cells[i]);
+ this.foldCells(children, collapse, recurse);
+ }
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED,
+ 'cells', cells, 'collapse', collapse, 'recurse', recurse));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: swapBounds
+ *
+ * Swaps the alternate and the actual bounds in the geometry of the given
+ * cell invoking <updateAlternateBounds> before carrying out the swap.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the bounds should be swapped.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.swapBounds = function(cell, willCollapse)
+{
+ if (cell != null)
+ {
+ var geo = this.model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+
+ this.updateAlternateBounds(cell, geo, willCollapse);
+ geo.swap();
+
+ this.model.setGeometry(cell, geo);
+ }
+ }
+};
+
+/**
+ * Function: updateAlternateBounds
+ *
+ * Updates or sets the alternate bounds in the given geometry for the given
+ * cell depending on whether the cell is going to be collapsed. If no
+ * alternate bounds are defined in the geometry and
+ * <collapseToPreferredSize> is true, then the preferred size is used for
+ * the alternate bounds. The top, left corner is always kept at the same
+ * location.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the geometry is being udpated.
+ * g - <mxGeometry> for which the alternate bounds should be updated.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.updateAlternateBounds = function(cell, geo, willCollapse)
+{
+ if (cell != null && geo != null)
+ {
+ if (geo.alternateBounds == null)
+ {
+ var bounds = geo;
+
+ if (this.collapseToPreferredSize)
+ {
+ var tmp = this.getPreferredSizeForCell(cell);
+
+ if (tmp != null)
+ {
+ bounds = tmp;
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ var startSize = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE);
+
+ if (startSize > 0)
+ {
+ bounds.height = Math.max(bounds.height, startSize);
+ }
+ }
+ }
+
+ geo.alternateBounds = new mxRectangle(
+ geo.x, geo.y, bounds.width, bounds.height);
+ }
+ else
+ {
+ geo.alternateBounds.x = geo.x;
+ geo.alternateBounds.y = geo.y;
+ }
+ }
+};
+
+/**
+ * Function: addAllEdges
+ *
+ * Returns an array with the given cells and all edges that are connected
+ * to a cell or one of its descendants.
+ */
+mxGraph.prototype.addAllEdges = function(cells)
+{
+ var allCells = cells.slice(); // FIXME: Required?
+ allCells = allCells.concat(this.getAllEdges(cells));
+
+ return allCells;
+};
+
+/**
+ * Function: getAllEdges
+ *
+ * Returns all edges connected to the given cells or its descendants.
+ */
+mxGraph.prototype.getAllEdges = function(cells)
+{
+ var edges = [];
+
+ if (cells != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var edgeCount = this.model.getEdgeCount(cells[i]);
+
+ for (var j = 0; j < edgeCount; j++)
+ {
+ edges.push(this.model.getEdgeAt(cells[i], j));
+ }
+
+ // Recurses
+ var children = this.model.getChildren(cells[i]);
+ edges = edges.concat(this.getAllEdges(children));
+ }
+ }
+
+ return edges;
+};
+
+/**
+ * Group: Cell sizing
+ */
+
+/**
+ * Function: updateCellSize
+ *
+ * Updates the size of the given cell in the model using <cellSizeUpdated>.
+ * This method fires <mxEvent.UPDATE_CELL_SIZE> while the transaction is in
+ * progress. Returns the cell whose size was updated.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose size should be updated.
+ */
+mxGraph.prototype.updateCellSize = function(cell, ignoreChildren)
+{
+ ignoreChildren = (ignoreChildren != null) ? ignoreChildren : false;
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellSizeUpdated(cell, ignoreChildren);
+ this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE,
+ 'cell', cell, 'ignoreChildren', ignoreChildren));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cell;
+};
+
+/**
+ * Function: cellSizeUpdated
+ *
+ * Updates the size of the given cell in the model using
+ * <getPreferredSizeForCell> to get the new size.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the size should be changed.
+ */
+mxGraph.prototype.cellSizeUpdated = function(cell, ignoreChildren)
+{
+ if (cell != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var size = this.getPreferredSizeForCell(cell);
+ var geo = this.model.getGeometry(cell);
+
+ if (size != null && geo != null)
+ {
+ var collapsed = this.isCellCollapsed(cell);
+ geo = geo.clone();
+
+ if (this.isSwimlane(cell))
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+ var cellStyle = this.model.getStyle(cell);
+
+ if (cellStyle == null)
+ {
+ cellStyle = '';
+ }
+
+ if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ cellStyle = mxUtils.setStyle(cellStyle,
+ mxConstants.STYLE_STARTSIZE, size.height + 8);
+
+ if (collapsed)
+ {
+ geo.height = size.height + 8;
+ }
+
+ geo.width = size.width;
+ }
+ else
+ {
+ cellStyle = mxUtils.setStyle(cellStyle,
+ mxConstants.STYLE_STARTSIZE, size.width + 8);
+
+ if (collapsed)
+ {
+ geo.width = size.width + 8;
+ }
+
+ geo.height = size.height;
+ }
+
+ this.model.setStyle(cell, cellStyle);
+ }
+ else
+ {
+ geo.width = size.width;
+ geo.height = size.height;
+ }
+
+ if (!ignoreChildren && !collapsed)
+ {
+ var bounds = this.view.getBounds(this.model.getChildren(cell));
+
+ if (bounds != null)
+ {
+ var tr = this.view.translate;
+ var scale = this.view.scale;
+
+ var width = (bounds.x + bounds.width) / scale - geo.x - tr.x;
+ var height = (bounds.y + bounds.height) / scale - geo.y - tr.y;
+
+ geo.width = Math.max(geo.width, width);
+ geo.height = Math.max(geo.height, height);
+ }
+ }
+
+ this.cellsResized([cell], [geo]);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: getPreferredSizeForCell
+ *
+ * Returns the preferred width and height of the given <mxCell> as an
+ * <mxRectangle>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the preferred size should be returned.
+ */
+mxGraph.prototype.getPreferredSizeForCell = function(cell)
+{
+ var result = null;
+
+ if (cell != null)
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ if (style != null && !this.model.isEdge(cell))
+ {
+ var fontSize = style[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE;
+ var dx = 0;
+ var dy = 0;
+
+ // Adds dimension of image if shape is a label
+ if (this.getImage(state) != null || style[mxConstants.STYLE_IMAGE] != null)
+ {
+ if (style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_LABEL)
+ {
+ if (style[mxConstants.STYLE_VERTICAL_ALIGN] == mxConstants.ALIGN_MIDDLE)
+ {
+ dx += parseFloat(style[mxConstants.STYLE_IMAGE_WIDTH]) || mxLabel.prototype.imageSize;
+ }
+
+ if (style[mxConstants.STYLE_ALIGN] != mxConstants.ALIGN_CENTER)
+ {
+ dy += parseFloat(style[mxConstants.STYLE_IMAGE_HEIGHT]) || mxLabel.prototype.imageSize;
+ }
+ }
+ }
+
+ // Adds spacings
+ dx += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+ dx += style[mxConstants.STYLE_SPACING_LEFT] || 0;
+ dx += style[mxConstants.STYLE_SPACING_RIGHT] || 0;
+
+ dy += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+ dy += style[mxConstants.STYLE_SPACING_TOP] || 0;
+ dy += style[mxConstants.STYLE_SPACING_BOTTOM] || 0;
+
+ // Add spacing for collapse/expand icon
+ // LATER: Check alignment and use constants
+ // for image spacing
+ var image = this.getFoldingImage(state);
+
+ if (image != null)
+ {
+ dx += image.width + 8;
+ }
+
+ // Adds space for label
+ var value = this.getLabel(cell);
+
+ if (value != null && value.length > 0)
+ {
+ if (!this.isHtmlLabel(cell))
+ {
+ value = value.replace(/\n/g, '<br>');
+ }
+
+ var size = mxUtils.getSizeForString(value,
+ fontSize, style[mxConstants.STYLE_FONTFAMILY]);
+ var width = size.width + dx;
+ var height = size.height + dy;
+
+ if (!mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ var tmp = height;
+
+ height = width;
+ width = tmp;
+ }
+
+ if (this.gridEnabled)
+ {
+ width = this.snap(width + this.gridSize / 2);
+ height = this.snap(height + this.gridSize / 2);
+ }
+
+ result = new mxRectangle(0, 0, width, height);
+ }
+ else
+ {
+ var gs2 = 4 * this.gridSize;
+ result = new mxRectangle(0, 0, gs2, gs2);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: handleGesture
+ *
+ * Invokes if a gesture event has been detected on a cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> which was pinched.
+ * evt - Object that represents the gesture event.
+ */
+mxGraph.prototype.handleGesture = function(state, evt)
+{
+ if (Math.abs(1 - evt.scale) > 0.2)
+ {
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+
+ var w = state.width * evt.scale;
+ var h = state.height * evt.scale;
+ var x = state.x - (w - state.width) / 2;
+ var y = state.y - (h - state.height) / 2;
+
+ var bounds = new mxRectangle(this.snap(x / scale) - tr.x,
+ this.snap(y / scale) - tr.y,
+ this.snap(w / scale), this.snap(h / scale));
+ this.resizeCell(state.cell, bounds);
+ }
+};
+
+/**
+ * Function: resizeCell
+ *
+ * Sets the bounds of the given cell using <resizeCells>. Returns the
+ * cell which was passed to the function.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose bounds should be changed.
+ * bounds - <mxRectangle> that represents the new bounds.
+ */
+mxGraph.prototype.resizeCell = function(cell, bounds)
+{
+ return this.resizeCells([cell], [bounds])[0];
+};
+
+/**
+ * Function: resizeCells
+ *
+ * Sets the bounds of the given cells and fires a <mxEvent.RESIZE_CELLS>
+ * event while the transaction is in progress. Returns the cells which
+ * have been passed to the function.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ */
+mxGraph.prototype.resizeCells = function(cells, bounds)
+{
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsResized(cells, bounds);
+ this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,
+ 'cells', cells, 'bounds', bounds));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsResized
+ *
+ * Sets the bounds of the given cells and fires a <mxEvent.CELLS_RESIZED>
+ * event. If <extendParents> is true, then the parent is extended if a
+ * child size is changed so that it overlaps with the parent.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ */
+mxGraph.prototype.cellsResized = function(cells, bounds)
+{
+ if (cells != null && bounds != null && cells.length == bounds.length)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var tmp = bounds[i];
+ var geo = this.model.getGeometry(cells[i]);
+
+ if (geo != null && (geo.x != tmp.x || geo.y != tmp.y ||
+ geo.width != tmp.width || geo.height != tmp.height))
+ {
+ geo = geo.clone();
+
+ if (geo.relative)
+ {
+ var offset = geo.offset;
+
+ if (offset != null)
+ {
+ offset.x += tmp.x - geo.x;
+ offset.y += tmp.y - geo.y;
+ }
+ }
+ else
+ {
+ geo.x = tmp.x;
+ geo.y = tmp.y;
+ }
+
+ geo.width = tmp.width;
+ geo.height = tmp.height;
+
+ if (!geo.relative && this.model.isVertex(cells[i]) &&
+ !this.isAllowNegativeCoordinates())
+ {
+ geo.x = Math.max(0, geo.x);
+ geo.y = Math.max(0, geo.y);
+ }
+
+ this.model.setGeometry(cells[i], geo);
+
+ if (this.isExtendParent(cells[i]))
+ {
+ this.extendParent(cells[i]);
+ }
+ }
+ }
+
+ if (this.resetEdgesOnResize)
+ {
+ this.resetEdges(cells);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED,
+ 'cells', cells, 'bounds', bounds));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: extendParent
+ *
+ * Resizes the parents recursively so that they contain the complete area
+ * of the resized child cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.extendParent = function(cell)
+{
+ if (cell != null)
+ {
+ var parent = this.model.getParent(cell);
+ var p = this.model.getGeometry(parent);
+
+ if (parent != null && p != null && !this.isCellCollapsed(parent))
+ {
+ var geo = this.model.getGeometry(cell);
+
+ if (geo != null && (p.width < geo.x + geo.width ||
+ p.height < geo.y + geo.height))
+ {
+ p = p.clone();
+
+ p.width = Math.max(p.width, geo.x + geo.width);
+ p.height = Math.max(p.height, geo.y + geo.height);
+
+ this.cellsResized([parent], [p]);
+ }
+ }
+ }
+};
+
+/**
+ * Group: Cell moving
+ */
+
+/**
+ * Function: importCells
+ *
+ * Clones and inserts the given cells into the graph using the move
+ * method and returns the inserted cells. This shortcut is used if
+ * cells are inserted via datatransfer.
+ */
+mxGraph.prototype.importCells = function(cells, dx, dy, target, evt)
+{
+ return this.moveCells(cells, dx, dy, true, target, evt);
+};
+
+/**
+ * Function: moveCells
+ *
+ * Moves or clones the specified cells and moves the cells or clones by the
+ * given amount, adding them to the optional target cell. The evt is the
+ * mouse event as the mouse was released. The change is carried out using
+ * <cellsMoved>. This method fires <mxEvent.MOVE_CELLS> while the
+ * transaction is in progress. Returns the cells that were moved.
+ *
+ * Use the following code to move all cells in the graph.
+ *
+ * (code)
+ * graph.moveCells(graph.getChildCells(null, true, true), 10, 10);
+ * (end)
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be moved, cloned or added to the target.
+ * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
+ * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
+ * clone - Boolean indicating if the cells should be cloned. Default is false.
+ * target - <mxCell> that represents the new parent of the cells.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.moveCells = function(cells, dx, dy, clone, target, evt)
+{
+ dx = (dx != null) ? dx : 0;
+ dy = (dy != null) ? dy : 0;
+ clone = (clone != null) ? clone : false;
+
+ if (cells != null && (dx != 0 || dy != 0 || clone || target != null))
+ {
+ this.model.beginUpdate();
+ try
+ {
+ if (clone)
+ {
+ cells = this.cloneCells(cells, this.isCloneInvalidEdges());
+
+ if (target == null)
+ {
+ target = this.getDefaultParent();
+ }
+ }
+
+ // FIXME: Cells should always be inserted first before any other edit
+ // to avoid forward references in sessions.
+ // Need to disable allowNegativeCoordinates if target not null to
+ // allow for temporary negative numbers until cellsAdded is called.
+ var previous = this.isAllowNegativeCoordinates();
+
+ if (target != null)
+ {
+ this.setAllowNegativeCoordinates(true);
+ }
+
+ this.cellsMoved(cells, dx, dy, !clone && this.isDisconnectOnMove()
+ && this.isAllowDanglingEdges(), target == null);
+
+ this.setAllowNegativeCoordinates(previous);
+
+ if (target != null)
+ {
+ var index = this.model.getChildCount(target);
+ this.cellsAdded(cells, target, index, null, null, true);
+ }
+
+ // Dispatches a move event
+ this.fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, 'cells', cells,
+ 'dx', dx, 'dy', dy, 'clone', clone, 'target', target, 'event', evt));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsMoved
+ *
+ * Moves the specified cells by the given vector, disconnecting the cells
+ * using disconnectGraph is disconnect is true. This method fires
+ * <mxEvent.CELLS_MOVED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsMoved = function(cells, dx, dy, disconnect, constrain)
+{
+ if (cells != null && (dx != 0 || dy != 0))
+ {
+ this.model.beginUpdate();
+ try
+ {
+ if (disconnect)
+ {
+ this.disconnectGraph(cells);
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ this.translateCell(cells[i], dx, dy);
+
+ if (constrain)
+ {
+ this.constrainChild(cells[i]);
+ }
+ }
+
+ if (this.resetEdgesOnMove)
+ {
+ this.resetEdges(cells);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED,
+ 'cells', cells, 'dx', dy, 'dy', dy, 'disconnect', disconnect));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: translateCell
+ *
+ * Translates the geometry of the given cell and stores the new,
+ * translated geometry in the model as an atomic change.
+ */
+mxGraph.prototype.translateCell = function(cell, dx, dy)
+{
+ var geo = this.model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ geo.translate(dx, dy);
+
+ if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())
+ {
+ geo.x = Math.max(0, geo.x);
+ geo.y = Math.max(0, geo.y);
+ }
+
+ if (geo.relative && !this.model.isEdge(cell))
+ {
+ if (geo.offset == null)
+ {
+ geo.offset = new mxPoint(dx, dy);
+ }
+ else
+ {
+ geo.offset.x += dx;
+ geo.offset.y += dy;
+ }
+ }
+
+ this.model.setGeometry(cell, geo);
+ }
+};
+
+/**
+ * Function: getCellContainmentArea
+ *
+ * Returns the <mxRectangle> inside which a cell is to be kept.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the area should be returned.
+ */
+mxGraph.prototype.getCellContainmentArea = function(cell)
+{
+ if (cell != null && !this.model.isEdge(cell))
+ {
+ var parent = this.model.getParent(cell);
+
+ if (parent == this.getDefaultParent() || parent == this.getCurrentRoot())
+ {
+ return this.getMaximumGraphBounds();
+ }
+ else if (parent != null && parent != this.getDefaultParent())
+ {
+ var g = this.model.getGeometry(parent);
+
+ if (g != null)
+ {
+ var x = 0;
+ var y = 0;
+ var w = g.width;
+ var h = g.height;
+
+ if (this.isSwimlane(parent))
+ {
+ var size = this.getStartSize(parent);
+
+ x = size.width;
+ w -= size.width;
+ y = size.height;
+ h -= size.height;
+ }
+
+ return new mxRectangle(x, y, w, h);
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: getMaximumGraphBounds
+ *
+ * Returns the bounds inside which the diagram should be kept as an
+ * <mxRectangle>.
+ */
+mxGraph.prototype.getMaximumGraphBounds = function()
+{
+ return this.maximumGraphBounds;
+};
+
+/**
+ * Function: constrainChild
+ *
+ * Keeps the given cell inside the bounds returned by
+ * <getCellContainmentArea> for its parent, according to the rules defined by
+ * <getOverlap> and <isConstrainChild>. This modifies the cell's geometry
+ * in-place and does not clone it.
+ *
+ * Parameters:
+ *
+ * cells - <mxCell> which should be constrained.
+ */
+mxGraph.prototype.constrainChild = function(cell)
+{
+ if (cell != null)
+ {
+ var geo = this.model.getGeometry(cell);
+ var area = (this.isConstrainChild(cell)) ?
+ this.getCellContainmentArea(cell) :
+ this.getMaximumGraphBounds();
+
+ if (geo != null && area != null)
+ {
+ // Keeps child within the content area of the parent
+ if (!geo.relative && (geo.x < area.x || geo.y < area.y ||
+ area.width < geo.x + geo.width || area.height < geo.y + geo.height))
+ {
+ var overlap = this.getOverlap(cell);
+
+ if (area.width > 0)
+ {
+ geo.x = Math.min(geo.x, area.x + area.width -
+ (1 - overlap) * geo.width);
+ }
+
+ if (area.height > 0)
+ {
+ geo.y = Math.min(geo.y, area.y + area.height -
+ (1 - overlap) * geo.height);
+ }
+
+ geo.x = Math.max(geo.x, area.x - geo.width * overlap);
+ geo.y = Math.max(geo.y, area.y - geo.height * overlap);
+ }
+ }
+ }
+};
+
+/**
+ * Function: resetEdges
+ *
+ * Resets the control points of the edges that are connected to the given
+ * cells if not both ends of the edge are in the given cells array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> for which the connected edges should be
+ * reset.
+ */
+mxGraph.prototype.resetEdges = function(cells)
+{
+ if (cells != null)
+ {
+ // Prepares a hashtable for faster cell lookups
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var edges = this.model.getEdges(cells[i]);
+
+ if (edges != null)
+ {
+ for (var j = 0; j < edges.length; j++)
+ {
+ var state = this.view.getState(edges[j]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[j], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[j], false);
+
+ var sourceId = mxCellPath.create(source);
+ var targetId = mxCellPath.create(target);
+
+ // Checks if one of the terminals is not in the given array
+ if (hash[sourceId] == null || hash[targetId] == null)
+ {
+ this.resetEdge(edges[j]);
+ }
+ }
+ }
+
+ this.resetEdges(this.model.getChildren(cells[i]));
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: resetEdge
+ *
+ * Resets the control points of the given edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose points should be reset.
+ */
+mxGraph.prototype.resetEdge = function(edge)
+{
+ var geo = this.model.getGeometry(edge);
+
+ // Resets the control points
+ if (geo != null && geo.points != null && geo.points.length > 0)
+ {
+ geo = geo.clone();
+ geo.points = [];
+ this.model.setGeometry(edge, geo);
+ }
+
+ return edge;
+};
+
+/**
+ * Group: Cell connecting and connection constraints
+ */
+
+/**
+ * Function: getAllConnectionConstraints
+ *
+ * Returns an array of all <mxConnectionConstraints> for the given terminal. If
+ * the shape of the given terminal is a <mxStencilShape> then the constraints
+ * of the corresponding <mxStencil> are returned.
+ *
+ * Parameters:
+ *
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the terminal is the source or target.
+ */
+mxGraph.prototype.getAllConnectionConstraints = function(terminal, source)
+{
+ if (terminal != null && terminal.shape != null &&
+ terminal.shape instanceof mxStencilShape)
+ {
+ if (terminal.shape.stencil != null)
+ {
+ return terminal.shape.stencil.constraints;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: getConnectionConstraint
+ *
+ * Returns an <mxConnectionConstraint> that describes the given connection
+ * point. This result can then be passed to <getConnectionPoint>.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> that represents the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ */
+mxGraph.prototype.getConnectionConstraint = function(edge, terminal, source)
+{
+ var point = null;
+ var x = edge.style[(source) ?
+ mxConstants.STYLE_EXIT_X :
+ mxConstants.STYLE_ENTRY_X];
+
+ if (x != null)
+ {
+ var y = edge.style[(source) ?
+ mxConstants.STYLE_EXIT_Y :
+ mxConstants.STYLE_ENTRY_Y];
+
+ if (y != null)
+ {
+ point = new mxPoint(parseFloat(x), parseFloat(y));
+ }
+ }
+
+ var perimeter = false;
+
+ if (point != null)
+ {
+ perimeter = mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, true);
+ }
+
+ return new mxConnectionConstraint(point, perimeter);
+};
+
+/**
+ * Function: setConnectionConstraint
+ *
+ * Sets the <mxConnectionConstraint> that describes the given connection point.
+ * If no constraint is given then nothing is changed. To remove an existing
+ * constraint from the given edge, use an empty constraint instead.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge.
+ * terminal - <mxCell> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.setConnectionConstraint = function(edge, terminal, source, constraint)
+{
+ if (constraint != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ if (constraint == null || constraint.point == null)
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+ mxConstants.STYLE_ENTRY_X, null, [edge]);
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+ mxConstants.STYLE_ENTRY_Y, null, [edge]);
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+ }
+ else if (constraint.point != null)
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+ mxConstants.STYLE_ENTRY_X, constraint.point.x, [edge]);
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+ mxConstants.STYLE_ENTRY_Y, constraint.point.y, [edge]);
+
+ // Only writes 0 since 1 is default
+ if (!constraint.perimeter)
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, '0', [edge]);
+ }
+ else
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+ }
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: getConnectionPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCellState> that represents the vertex.
+ * constraint - <mxConnectionConstraint> that represents the connection point
+ * constraint as returned by <getConnectionConstraint>.
+ */
+mxGraph.prototype.getConnectionPoint = function(vertex, constraint)
+{
+ var point = null;
+
+ if (vertex != null)
+ {
+ var bounds = this.view.getPerimeterBounds(vertex);
+ var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+
+ var direction = vertex.style[mxConstants.STYLE_DIRECTION];
+ var r1 = 0;
+
+ // Bounds need to be rotated by 90 degrees for further computation
+ if (direction != null)
+ {
+ if (direction == 'north')
+ {
+ r1 += 270;
+ }
+ else if (direction == 'west')
+ {
+ r1 += 180;
+ }
+ else if (direction == 'south')
+ {
+ r1 += 90;
+ }
+
+ // Bounds need to be rotated by 90 degrees for further computation
+ if (direction == 'north' || direction == 'south')
+ {
+ bounds.x += bounds.width / 2 - bounds.height / 2;
+ bounds.y += bounds.height / 2 - bounds.width / 2;
+ var tmp = bounds.width;
+ bounds.width = bounds.height;
+ bounds.height = tmp;
+ }
+ }
+
+ if (constraint.point != null)
+ {
+ var sx = 1;
+ var sy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ // LATER: Add flipping support for image shapes
+ if (vertex.shape instanceof mxStencilShape)
+ {
+ var flipH = vertex.style[mxConstants.STYLE_STENCIL_FLIPH];
+ var flipV = vertex.style[mxConstants.STYLE_STENCIL_FLIPV];
+
+ if (direction == 'north' || direction == 'south')
+ {
+ var tmp = flipH;
+ flipH = flipV;
+ flipV = tmp;
+ }
+
+ if (flipH)
+ {
+ sx = -1;
+ dx = -bounds.width;
+ }
+
+ if (flipV)
+ {
+ sy = -1;
+ dy = -bounds.height ;
+ }
+ }
+
+ point = new mxPoint(bounds.x + constraint.point.x * bounds.width * sx - dx,
+ bounds.y + constraint.point.y * bounds.height * sy - dy);
+ }
+
+ // Rotation for direction before projection on perimeter
+ var r2 = vertex.style[mxConstants.STYLE_ROTATION] || 0;
+
+ if (constraint.perimeter)
+ {
+ if (r1 != 0 && point != null)
+ {
+ // Only 90 degrees steps possible here so no trig needed
+ var cos = 0;
+ var sin = 0;
+
+ if (r1 == 90)
+ {
+ sin = 1;
+ }
+ else if (r1 == 180)
+ {
+ cos = -1;
+ }
+ else if (r2 == 270)
+ {
+ sin = -1;
+ }
+
+ point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+ }
+
+ if (point != null && constraint.perimeter)
+ {
+ point = this.view.getPerimeterPoint(vertex, point, false);
+ }
+ }
+ else
+ {
+ r2 += r1;
+ }
+
+ // Generic rotation after projection on perimeter
+ if (r2 != 0 && point != null)
+ {
+ var rad = mxUtils.toRadians(r2);
+ var cos = Math.cos(rad);
+ var sin = Math.sin(rad);
+
+ point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+ }
+ }
+
+ return point;
+};
+
+/**
+ * Function: connectCell
+ *
+ * Connects the specified end of the given edge to the given terminal
+ * using <cellConnected> and fires <mxEvent.CONNECT_CELL> while the
+ * transaction is in progress. Returns the updated edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.connectCell = function(edge, terminal, source, constraint)
+{
+ this.model.beginUpdate();
+ try
+ {
+ var previous = this.model.getTerminal(edge, source);
+ this.cellConnected(edge, terminal, source, constraint);
+ this.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL,
+ 'edge', edge, 'terminal', terminal, 'source', source,
+ 'previous', previous));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return edge;
+};
+
+/**
+ * Function: cellConnected
+ *
+ * Sets the new terminal for the given edge and resets the edge points if
+ * <resetEdgesOnConnect> is true. This method fires
+ * <mxEvent.CELL_CONNECTED> while the transaction is in progress.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - <mxConnectionConstraint> to be used for this connection.
+ */
+mxGraph.prototype.cellConnected = function(edge, terminal, source, constraint)
+{
+ if (edge != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var previous = this.model.getTerminal(edge, source);
+
+ // Updates the constraint
+ this.setConnectionConstraint(edge, terminal, source, constraint);
+
+ // Checks if the new terminal is a port, uses the ID of the port in the
+ // style and the parent of the port as the actual terminal of the edge.
+ if (this.isPortsEnabled())
+ {
+ var id = null;
+
+ if (this.isPort(terminal))
+ {
+ id = terminal.getId();
+ terminal = this.getTerminalForPort(terminal, source);
+ }
+
+ // Sets or resets all previous information for connecting to a child port
+ var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+ mxConstants.STYLE_TARGET_PORT;
+ this.setCellStyles(key, id, [edge]);
+ }
+
+ this.model.setTerminal(edge, terminal, source);
+
+ if (this.resetEdgesOnConnect)
+ {
+ this.resetEdge(edge);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED,
+ 'edge', edge, 'terminal', terminal, 'source', source,
+ 'previous', previous));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: disconnectGraph
+ *
+ * Disconnects the given edges from the terminals which are not in the
+ * given array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be disconnected.
+ */
+mxGraph.prototype.disconnectGraph = function(cells)
+{
+ if (cells != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+
+ // Prepares a hashtable for faster cell lookups
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.model.isEdge(cells[i]))
+ {
+ var geo = this.model.getGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var state = this.view.getState(cells[i]);
+ var pstate = this.view.getState(
+ this.model.getParent(cells[i]));
+
+ if (state != null &&
+ pstate != null)
+ {
+ geo = geo.clone();
+
+ var dx = -pstate.origin.x;
+ var dy = -pstate.origin.y;
+ var pts = state.absolutePoints;
+
+ var src = this.model.getTerminal(cells[i], true);
+
+ if (src != null && this.isCellDisconnectable(cells[i], src, true))
+ {
+ var srcId = mxCellPath.create(src);
+
+ while (src != null && hash[srcId] == null)
+ {
+ src = this.model.getParent(src);
+ srcId = mxCellPath.create(src);
+ }
+
+ if (src == null)
+ {
+ geo.setTerminalPoint(
+ new mxPoint(pts[0].x / scale - tr.x + dx,
+ pts[0].y / scale - tr.y + dy), true);
+ this.model.setTerminal(cells[i], null, true);
+ }
+ }
+
+ var trg = this.model.getTerminal(cells[i], false);
+
+ if (trg != null && this.isCellDisconnectable(cells[i], trg, false))
+ {
+ var trgId = mxCellPath.create(trg);
+
+ while (trg != null && hash[trgId] == null)
+ {
+ trg = this.model.getParent(trg);
+ trgId = mxCellPath.create(trg);
+ }
+
+ if (trg == null)
+ {
+ var n = pts.length - 1;
+ geo.setTerminalPoint(
+ new mxPoint(pts[n].x / scale - tr.x + dx,
+ pts[n].y / scale - tr.y + dy), false);
+ this.model.setTerminal(cells[i], null, false);
+ }
+ }
+
+ this.model.setGeometry(cells[i], geo);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Group: Drilldown
+ */
+
+/**
+ * Function: getCurrentRoot
+ *
+ * Returns the current root of the displayed cell hierarchy. This is a
+ * shortcut to <mxGraphView.currentRoot> in <view>.
+ */
+ mxGraph.prototype.getCurrentRoot = function()
+ {
+ return this.view.currentRoot;
+ };
+
+ /**
+ * Function: getTranslateForRoot
+ *
+ * Returns the translation to be used if the given cell is the root cell as
+ * an <mxPoint>. This implementation returns null.
+ *
+ * Example:
+ *
+ * To keep the children at their absolute position while stepping into groups,
+ * this function can be overridden as follows.
+ *
+ * (code)
+ * var offset = new mxPoint(0, 0);
+ *
+ * while (cell != null)
+ * {
+ * var geo = this.model.getGeometry(cell);
+ *
+ * if (geo != null)
+ * {
+ * offset.x -= geo.x;
+ * offset.y -= geo.y;
+ * }
+ *
+ * cell = this.model.getParent(cell);
+ * }
+ *
+ * return offset;
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the root.
+ */
+mxGraph.prototype.getTranslateForRoot = function(cell)
+{
+ return null;
+};
+
+/**
+ * Function: isPort
+ *
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, the cell returned by getTerminalForPort should be used as the
+ * terminal and the port should be referenced by the ID in either the
+ * mxConstants.STYLE_SOURCE_PORT or the or the
+ * mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable.
+ * This implementation always returns false.
+ *
+ * A typical implementation is the following:
+ *
+ * (code)
+ * graph.isPort = function(cell)
+ * {
+ * var geo = this.getCellGeometry(cell);
+ *
+ * return (geo != null) ? geo.relative : false;
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the port.
+ */
+mxGraph.prototype.isPort = function(cell)
+{
+ return false;
+};
+
+/**
+ * Function: getTerminalForPort
+ *
+ * Returns the terminal to be used for a given port. This implementation
+ * always returns the parent cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the port.
+ * source - If the cell is the source or target port.
+ */
+mxGraph.prototype.getTerminalForPort = function(cell, source)
+{
+ return this.model.getParent(cell);
+};
+
+/**
+ * Function: getChildOffsetForCell
+ *
+ * Returns the offset to be used for the cells inside the given cell. The
+ * root and layer cells may be identified using <mxGraphModel.isRoot> and
+ * <mxGraphModel.isLayer>. For all other current roots, the
+ * <mxGraphView.currentRoot> field points to the respective cell, so that
+ * the following holds: cell == this.view.currentRoot. This implementation
+ * returns null.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose offset should be returned.
+ */
+mxGraph.prototype.getChildOffsetForCell = function(cell)
+{
+ return null;
+};
+
+/**
+ * Function: enterGroup
+ *
+ * Uses the given cell as the root of the displayed cell hierarchy. If no
+ * cell is specified then the selection cell is used. The cell is only used
+ * if <isValidRoot> returns true.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> to be used as the new root. Default is the
+ * selection cell.
+ */
+mxGraph.prototype.enterGroup = function(cell)
+{
+ cell = cell || this.getSelectionCell();
+
+ if (cell != null && this.isValidRoot(cell))
+ {
+ this.view.setCurrentRoot(cell);
+ this.clearSelection();
+ }
+};
+
+/**
+ * Function: exitGroup
+ *
+ * Changes the current root to the next valid root in the displayed cell
+ * hierarchy.
+ */
+mxGraph.prototype.exitGroup = function()
+{
+ var root = this.model.getRoot();
+ var current = this.getCurrentRoot();
+
+ if (current != null)
+ {
+ var next = this.model.getParent(current);
+
+ // Finds the next valid root in the hierarchy
+ while (next != root && !this.isValidRoot(next) &&
+ this.model.getParent(next) != root)
+ {
+ next = this.model.getParent(next);
+ }
+
+ // Clears the current root if the new root is
+ // the model's root or one of the layers.
+ if (next == root || this.model.getParent(next) == root)
+ {
+ this.view.setCurrentRoot(null);
+ }
+ else
+ {
+ this.view.setCurrentRoot(next);
+ }
+
+ var state = this.view.getState(current);
+
+ // Selects the previous root in the graph
+ if (state != null)
+ {
+ this.setSelectionCell(current);
+ }
+ }
+};
+
+/**
+ * Function: home
+ *
+ * Uses the root of the model as the root of the displayed cell hierarchy
+ * and selects the previous root.
+ */
+mxGraph.prototype.home = function()
+{
+ var current = this.getCurrentRoot();
+
+ if (current != null)
+ {
+ this.view.setCurrentRoot(null);
+ var state = this.view.getState(current);
+
+ if (state != null)
+ {
+ this.setSelectionCell(current);
+ }
+ }
+};
+
+/**
+ * Function: isValidRoot
+ *
+ * Returns true if the given cell is a valid root for the cell display
+ * hierarchy. This implementation returns true for all non-null values.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> which should be checked as a possible root.
+ */
+mxGraph.prototype.isValidRoot = function(cell)
+{
+ return (cell != null);
+};
+
+/**
+ * Group: Graph display
+ */
+
+/**
+ * Function: getGraphBounds
+ *
+ * Returns the bounds of the visible graph. Shortcut to
+ * <mxGraphView.getGraphBounds>. See also: <getBoundingBoxFromGeometry>.
+ */
+ mxGraph.prototype.getGraphBounds = function()
+ {
+ return this.view.getGraphBounds();
+ };
+
+/**
+ * Function: getCellBounds
+ *
+ * Returns the scaled, translated bounds for the given cell. See
+ * <mxGraphView.getBounds> for arrays.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose bounds should be returned.
+ * includeEdge - Optional boolean that specifies if the bounds of
+ * the connected edges should be included. Default is false.
+ * includeDescendants - Optional boolean that specifies if the bounds
+ * of all descendants should be included. Default is false.
+ */
+mxGraph.prototype.getCellBounds = function(cell, includeEdges, includeDescendants)
+{
+ var cells = [cell];
+
+ // Includes all connected edges
+ if (includeEdges)
+ {
+ cells = cells.concat(this.model.getEdges(cell));
+ }
+
+ var result = this.view.getBounds(cells);
+
+ // Recursively includes the bounds of the children
+ if (includeDescendants)
+ {
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var tmp = this.getCellBounds(this.model.getChildAt(cell, i),
+ includeEdges, true);
+
+ if (result != null)
+ {
+ result.add(tmp);
+ }
+ else
+ {
+ result = tmp;
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getBoundingBoxFromGeometry
+ *
+ * Returns the bounding box for the geometries of the vertices in the
+ * given array of cells. This can be used to find the graph bounds during
+ * a layout operation (ie. before the last endUpdate) as follows:
+ *
+ * (code)
+ * var cells = graph.getChildCells(graph.getDefaultParent(), true, true);
+ * var bounds = graph.getBoundingBoxFromGeometry(cells, true);
+ * (end)
+ *
+ * This can then be used to move cells to the origin:
+ *
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ * graph.moveCells(cells, -Math.min(bounds.x, 0), -Math.min(bounds.y, 0))
+ * }
+ * (end)
+ *
+ * Or to translate the graph view:
+ *
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ * graph.view.setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0));
+ * }
+ * (end)
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be returned.
+ * includeEdges - Specifies if edge bounds should be included by computing
+ * the bounding box for all points its geometry. Default is false.
+ */
+mxGraph.prototype.getBoundingBoxFromGeometry = function(cells, includeEdges)
+{
+ includeEdges = (includeEdges != null) ? includeEdges : false;
+ var result = null;
+
+ if (cells != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (includeEdges || this.model.isVertex(cells[i]))
+ {
+ // Computes the bounding box for the points in the geometry
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var pts = geo.points;
+
+ if (pts != null && pts.length > 0)
+ {
+ var tmp = new mxRectangle(pts[0].x, pts[0].y, 0, 0);
+ var addPoint = function(pt)
+ {
+ if (pt != null)
+ {
+ tmp.add(new mxRectangle(pt.x, pt.y, 0, 0));
+ }
+ };
+
+ for (var j = 1; j < pts.length; j++)
+ {
+ addPoint(pts[j]);
+ }
+
+ addPoint(geo.getTerminalPoint(true));
+ addPoint(geo.getTerminalPoint(false));
+ }
+
+ if (result == null)
+ {
+ result = new mxRectangle(geo.x, geo.y, geo.width, geo.height);
+ }
+ else
+ {
+ result.add(geo);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: refresh
+ *
+ * Clears all cell states or the states for the hierarchy starting at the
+ * given cell and validates the graph. This fires a refresh event as the
+ * last step.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> for which the cell states should be cleared.
+ */
+mxGraph.prototype.refresh = function(cell)
+{
+ this.view.clear(cell, cell == null);
+ this.view.validate();
+ this.sizeDidChange();
+ this.fireEvent(new mxEventObject(mxEvent.REFRESH));
+};
+
+/**
+ * Function: snap
+ *
+ * Snaps the given numeric value to the grid if <gridEnabled> is true.
+ *
+ * Parameters:
+ *
+ * value - Numeric value to be snapped to the grid.
+ */
+mxGraph.prototype.snap = function(value)
+{
+ if (this.gridEnabled)
+ {
+ value = Math.round(value / this.gridSize ) * this.gridSize;
+ }
+
+ return value;
+};
+
+/**
+ * Function: panGraph
+ *
+ * Shifts the graph display by the given amount. This is used to preview
+ * panning operations, use <mxGraphView.setTranslate> to set a persistent
+ * translation of the view. Fires <mxEvent.PAN>.
+ *
+ * Parameters:
+ *
+ * dx - Amount to shift the graph along the x-axis.
+ * dy - Amount to shift the graph along the y-axis.
+ */
+mxGraph.prototype.panGraph = function(dx, dy)
+{
+ if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container))
+ {
+ this.container.scrollLeft = -dx;
+ this.container.scrollTop = -dy;
+ }
+ else
+ {
+ var canvas = this.view.getCanvas();
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ // Puts everything inside the container in a DIV so that it
+ // can be moved without changing the state of the container
+ if (dx == 0 && dy == 0)
+ {
+ // Workaround for ignored removeAttribute on SVG element in IE9 standards
+ if (mxClient.IS_IE)
+ {
+ canvas.setAttribute('transform', 'translate('+ dx + ',' + dy + ')');
+ }
+ else
+ {
+ canvas.removeAttribute('transform');
+ }
+
+ if (this.shiftPreview1 != null)
+ {
+ var child = this.shiftPreview1.firstChild;
+
+ while (child != null)
+ {
+ var next = child.nextSibling;
+ this.container.appendChild(child);
+ child = next;
+ }
+
+ this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);
+ this.shiftPreview1 = null;
+
+ this.container.appendChild(canvas.parentNode);
+
+ child = this.shiftPreview2.firstChild;
+
+ while (child != null)
+ {
+ var next = child.nextSibling;
+ this.container.appendChild(child);
+ child = next;
+ }
+
+ this.shiftPreview2.parentNode.removeChild(this.shiftPreview2);
+ this.shiftPreview2 = null;
+ }
+ }
+ else
+ {
+ canvas.setAttribute('transform', 'translate('+ dx + ',' + dy + ')');
+
+ if (this.shiftPreview1 == null)
+ {
+ // Needs two divs for stuff before and after the SVG element
+ this.shiftPreview1 = document.createElement('div');
+ this.shiftPreview1.style.position = 'absolute';
+ this.shiftPreview1.style.overflow = 'visible';
+
+ this.shiftPreview2 = document.createElement('div');
+ this.shiftPreview2.style.position = 'absolute';
+ this.shiftPreview2.style.overflow = 'visible';
+
+ var current = this.shiftPreview1;
+ var child = this.container.firstChild;
+
+ while (child != null)
+ {
+ var next = child.nextSibling;
+
+ // SVG element is moved via transform attribute
+ if (child != canvas.parentNode)
+ {
+ current.appendChild(child);
+ }
+ else
+ {
+ current = this.shiftPreview2;
+ }
+
+ child = next;
+ }
+
+ this.container.insertBefore(this.shiftPreview1, canvas.parentNode);
+ this.container.appendChild(this.shiftPreview2);
+ }
+
+ this.shiftPreview1.style.left = dx + 'px';
+ this.shiftPreview1.style.top = dy + 'px';
+ this.shiftPreview2.style.left = dx + 'px';
+ this.shiftPreview2.style.top = dy + 'px';
+ }
+ }
+ else
+ {
+ canvas.style.left = dx + 'px';
+ canvas.style.top = dy + 'px';
+ }
+
+ this.panDx = dx;
+ this.panDy = dy;
+
+ this.fireEvent(new mxEventObject(mxEvent.PAN));
+ }
+};
+
+/**
+ * Function: zoomIn
+ *
+ * Zooms into the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomIn = function()
+{
+ this.zoom(this.zoomFactor);
+};
+
+/**
+ * Function: zoomOut
+ *
+ * Zooms out of the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomOut = function()
+{
+ this.zoom(1 / this.zoomFactor);
+};
+
+/**
+ * Function: zoomActual
+ *
+ * Resets the zoom and panning in the view.
+ */
+mxGraph.prototype.zoomActual = function()
+{
+ if (this.view.scale == 1)
+ {
+ this.view.setTranslate(0, 0);
+ }
+ else
+ {
+ this.view.translate.x = 0;
+ this.view.translate.y = 0;
+
+ this.view.setScale(1);
+ }
+};
+
+/**
+ * Function: zoomTo
+ *
+ * Zooms the graph to the given scale with an optional boolean center
+ * argument, which is passd to <zoom>.
+ */
+mxGraph.prototype.zoomTo = function(scale, center)
+{
+ this.zoom(scale / this.view.scale, center);
+};
+
+/**
+ * Function: zoom
+ *
+ * Zooms the graph using the given factor. Center is an optional boolean
+ * argument that keeps the graph scrolled to the center. If the center argument
+ * is omitted, then <centerZoom> will be used as its value.
+ */
+mxGraph.prototype.zoom = function(factor, center)
+{
+ center = (center != null) ? center : this.centerZoom;
+ var scale = this.view.scale * factor;
+ var state = this.view.getState(this.getSelectionCell());
+
+ if (this.keepSelectionVisibleOnZoom && state != null)
+ {
+ var rect = new mxRectangle(
+ state.x * factor,
+ state.y * factor,
+ state.width * factor,
+ state.height * factor);
+
+ // Refreshes the display only once if a
+ // scroll is carried out
+ this.view.scale = scale;
+
+ if (!this.scrollRectToVisible(rect))
+ {
+ this.view.revalidate();
+
+ // Forces an event to be fired but does not revalidate again
+ this.view.setScale(scale);
+ }
+ }
+ else if (center && !mxUtils.hasScrollbars(this.container))
+ {
+ var dx = this.container.offsetWidth;
+ var dy = this.container.offsetHeight;
+
+ if (factor > 1)
+ {
+ var f = (factor -1) / (scale * 2);
+ dx *= -f;
+ dy *= -f;
+ }
+ else
+ {
+ var f = (1/factor -1) / (this.view.scale * 2);
+ dx *= f;
+ dy *= f;
+ }
+
+ this.view.scaleAndTranslate(scale,
+ this.view.translate.x + dx,
+ this.view.translate.y + dy);
+ }
+ else
+ {
+ this.view.setScale(scale);
+
+ if (mxUtils.hasScrollbars(this.container))
+ {
+ var dx = 0;
+ var dy = 0;
+
+ if (center)
+ {
+ dx = this.container.offsetWidth * (factor - 1) / 2;
+ dy = this.container.offsetHeight * (factor - 1) / 2;
+ }
+
+ this.container.scrollLeft = Math.round(this.container.scrollLeft * factor + dx);
+ this.container.scrollTop = Math.round(this.container.scrollTop * factor + dy);
+ }
+ }
+};
+
+/**
+ * Function: zoomToRect
+ *
+ * Zooms the graph to the specified rectangle. If the rectangle does not have same aspect
+ * ratio as the display container, it is increased in the smaller relative dimension only
+ * until the aspect match. The original rectangle is centralised within this expanded one.
+ *
+ * Note that the input rectangular must be un-scaled and un-translated.
+ *
+ * Parameters:
+ *
+ * rect - The un-scaled and un-translated rectangluar region that should be just visible
+ * after the operation
+ */
+mxGraph.prototype.zoomToRect = function(rect)
+{
+ var scaleX = this.container.clientWidth / rect.width;
+ var scaleY = this.container.clientHeight / rect.height;
+ var aspectFactor = scaleX / scaleY;
+
+ // Remove any overlap of the rect outside the client area
+ rect.x = Math.max(0, rect.x);
+ rect.y = Math.max(0, rect.y);
+ var rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+ var rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+ rect.width = rectRight - rect.x;
+ rect.height = rectBottom - rect.y;
+
+ // The selection area has to be increased to the same aspect
+ // ratio as the container, centred around the centre point of the
+ // original rect passed in.
+ if (aspectFactor < 1.0)
+ {
+ // Height needs increasing
+ var newHeight = rect.height / aspectFactor;
+ var deltaHeightBuffer = (newHeight - rect.height) / 2.0;
+ rect.height = newHeight;
+
+ // Assign up to half the buffer to the upper part of the rect, not crossing 0
+ // put the rest on the bottom
+ var upperBuffer = Math.min(rect.y , deltaHeightBuffer);
+ rect.y = rect.y - upperBuffer;
+
+ // Check if the bottom has extended too far
+ rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+ rect.height = rectBottom - rect.y;
+ }
+ else
+ {
+ // Width needs increasing
+ var newWidth = rect.width * aspectFactor;
+ var deltaWidthBuffer = (newWidth - rect.width) / 2.0;
+ rect.width = newWidth;
+
+ // Assign up to half the buffer to the upper part of the rect, not crossing 0
+ // put the rest on the bottom
+ var leftBuffer = Math.min(rect.x , deltaWidthBuffer);
+ rect.x = rect.x - leftBuffer;
+
+ // Check if the right hand side has extended too far
+ rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+ rect.width = rectRight - rect.x;
+ }
+
+ var scale = this.container.clientWidth / rect.width;
+
+ if (!mxUtils.hasScrollbars(this.container))
+ {
+ this.view.scaleAndTranslate(scale, -rect.x, -rect.y);
+ }
+ else
+ {
+ this.view.setScale(scale);
+ this.container.scrollLeft = Math.round(rect.x * scale);
+ this.container.scrollTop = Math.round(rect.y * scale);
+ }
+};
+
+/**
+ * Function: fit
+ *
+ * Scales the graph such that the complete diagram fits into <container> and
+ * returns the current scale in the view. To fit an initial graph prior to
+ * rendering, set <mxGraphView.rendering> to false prior to changing the model
+ * and execute the following after changing the model.
+ *
+ * (code)
+ * graph.fit();
+ * graph.view.rendering = true;
+ * graph.refresh();
+ * (end)
+ *
+ * Parameters:
+ *
+ * border - Optional number that specifies the border. Default is 0.
+ * keepOrigin - Optional boolean that specifies if the translate should be
+ * changed. Default is false.
+ */
+mxGraph.prototype.fit = function(border, keepOrigin)
+{
+ if (this.container != null)
+ {
+ border = (border != null) ? border : 0;
+ keepOrigin = (keepOrigin != null) ? keepOrigin : false;
+
+ var w1 = this.container.clientWidth;
+ var h1 = this.container.clientHeight;
+
+ var bounds = this.view.getGraphBounds();
+
+ if (keepOrigin && bounds.x != null && bounds.y != null)
+ {
+ bounds.width += bounds.x;
+ bounds.height += bounds.y;
+ bounds.x = 0;
+ bounds.y = 0;
+ }
+
+ var s = this.view.scale;
+ var w2 = bounds.width / s;
+ var h2 = bounds.height / s;
+
+ // Fits to the size of the background image if required
+ if (this.backgroundImage != null)
+ {
+ w2 = Math.max(w2, this.backgroundImage.width - bounds.x / s);
+ h2 = Math.max(h2, this.backgroundImage.height - bounds.y / s);
+ }
+
+ var b = (keepOrigin) ? border : 2 * border;
+ var s2 = Math.floor(Math.min(w1 / (w2 + b), h1 / (h2 + b)) * 100) / 100;
+
+ if (this.minFitScale != null)
+ {
+ s2 = Math.max(s2, this.minFitScale);
+ }
+
+ if (this.maxFitScale != null)
+ {
+ s2 = Math.min(s2, this.maxFitScale);
+ }
+
+ if (!keepOrigin)
+ {
+ if (!mxUtils.hasScrollbars(this.container))
+ {
+ var x0 = (bounds.x != null) ? Math.floor(this.view.translate.x - bounds.x / s + border + 1) : border;
+ var y0 = (bounds.y != null) ? Math.floor(this.view.translate.y - bounds.y / s + border + 1) : border;
+
+ this.view.scaleAndTranslate(s2, x0, y0);
+ }
+ else
+ {
+ this.view.setScale(s2);
+
+ if (bounds.x != null)
+ {
+ this.container.scrollLeft = Math.round(bounds.x / s) * s2 - border -
+ Math.max(0, (this.container.clientWidth - w2 * s2) / 2);
+ }
+
+ if (bounds.y != null)
+ {
+ this.container.scrollTop = Math.round(bounds.y / s) * s2 - border -
+ Math.max(0, (this.container.clientHeight - h2 * s2) / 2);
+ }
+ }
+ }
+ else if (this.view.scale != s2)
+ {
+ this.view.setScale(s2);
+ }
+ }
+
+ return this.view.scale;
+};
+
+/**
+ * Function: scrollCellToVisible
+ *
+ * Pans the graph so that it shows the given cell. Optionally the cell may
+ * be centered in the container.
+ *
+ * To center a given graph if the <container> has no scrollbars, use the following code.
+ *
+ * [code]
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x - (bounds.width - container.clientWidth) / 2,
+ * -bounds.y - (bounds.height - container.clientHeight) / 2);
+ * [/code]
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be made visible.
+ * center - Optional boolean flag. Default is false.
+ */
+mxGraph.prototype.scrollCellToVisible = function(cell, center)
+{
+ var x = -this.view.translate.x;
+ var y = -this.view.translate.y;
+
+ var state = this.view.getState(cell);
+
+ if (state != null)
+ {
+ var bounds = new mxRectangle(x + state.x, y + state.y, state.width,
+ state.height);
+
+ if (center && this.container != null)
+ {
+ var w = this.container.clientWidth;
+ var h = this.container.clientHeight;
+
+ bounds.x = bounds.getCenterX() - w / 2;
+ bounds.width = w;
+ bounds.y = bounds.getCenterY() - h / 2;
+ bounds.height = h;
+ }
+
+ if (this.scrollRectToVisible(bounds))
+ {
+ // Triggers an update via the view's event source
+ this.view.setTranslate(this.view.translate.x, this.view.translate.y);
+ }
+ }
+};
+
+/**
+ * Function: scrollRectToVisible
+ *
+ * Pans the graph so that it shows the given rectangle.
+ *
+ * Parameters:
+ *
+ * rect - <mxRectangle> to be made visible.
+ */
+mxGraph.prototype.scrollRectToVisible = function(rect)
+{
+ var isChanged = false;
+
+ if (rect != null)
+ {
+ var w = this.container.offsetWidth;
+ var h = this.container.offsetHeight;
+
+ var widthLimit = Math.min(w, rect.width);
+ var heightLimit = Math.min(h, rect.height);
+
+ if (mxUtils.hasScrollbars(this.container))
+ {
+ var c = this.container;
+ rect.x += this.view.translate.x;
+ rect.y += this.view.translate.y;
+ var dx = c.scrollLeft - rect.x;
+ var ddx = Math.max(dx - c.scrollLeft, 0);
+
+ if (dx > 0)
+ {
+ c.scrollLeft -= dx + 2;
+ }
+ else
+ {
+ dx = rect.x + widthLimit - c.scrollLeft - c.clientWidth;
+
+ if (dx > 0)
+ {
+ c.scrollLeft += dx + 2;
+ }
+ }
+
+ var dy = c.scrollTop - rect.y;
+ var ddy = Math.max(0, dy - c.scrollTop);
+
+ if (dy > 0)
+ {
+ c.scrollTop -= dy + 2;
+ }
+ else
+ {
+ dy = rect.y + heightLimit - c.scrollTop - c.clientHeight;
+
+ if (dy > 0)
+ {
+ c.scrollTop += dy + 2;
+ }
+ }
+
+ if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0))
+ {
+ this.view.setTranslate(ddx, ddy);
+ }
+ }
+ else
+ {
+ var x = -this.view.translate.x;
+ var y = -this.view.translate.y;
+
+ var s = this.view.scale;
+
+ if (rect.x + widthLimit > x + w)
+ {
+ this.view.translate.x -= (rect.x + widthLimit - w - x) / s;
+ isChanged = true;
+ }
+
+ if (rect.y + heightLimit > y + h)
+ {
+ this.view.translate.y -= (rect.y + heightLimit - h - y) / s;
+ isChanged = true;
+ }
+
+ if (rect.x < x)
+ {
+ this.view.translate.x += (x - rect.x) / s;
+ isChanged = true;
+ }
+
+ if (rect.y < y)
+ {
+ this.view.translate.y += (y - rect.y) / s;
+ isChanged = true;
+ }
+
+ if (isChanged)
+ {
+ this.view.refresh();
+
+ // Repaints selection marker (ticket 18)
+ if (this.selectionCellsHandler != null)
+ {
+ this.selectionCellsHandler.refresh();
+ }
+ }
+ }
+ }
+
+ return isChanged;
+};
+
+/**
+ * Function: getCellGeometry
+ *
+ * Returns the <mxGeometry> for the given cell. This implementation uses
+ * <mxGraphModel.getGeometry>. Subclasses can override this to implement
+ * specific geometries for cells in only one graph, that is, it can return
+ * geometries that depend on the current state of the view.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraph.prototype.getCellGeometry = function(cell)
+{
+ return this.model.getGeometry(cell);
+};
+
+/**
+ * Function: isCellVisible
+ *
+ * Returns true if the given cell is visible in this graph. This
+ * implementation uses <mxGraphModel.isVisible>. Subclassers can override
+ * this to implement specific visibility for cells in only one graph, that
+ * is, without affecting the visible state of the cell.
+ *
+ * When using dynamic filter expressions for cell visibility, then the
+ * graph should be revalidated after the filter expression has changed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraph.prototype.isCellVisible = function(cell)
+{
+ return this.model.isVisible(cell);
+};
+
+/**
+ * Function: isCellCollapsed
+ *
+ * Returns true if the given cell is collapsed in this graph. This
+ * implementation uses <mxGraphModel.isCollapsed>. Subclassers can override
+ * this to implement specific collapsed states for cells in only one graph,
+ * that is, without affecting the collapsed state of the cell.
+ *
+ * When using dynamic filter expressions for the collapsed state, then the
+ * graph should be revalidated after the filter expression has changed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraph.prototype.isCellCollapsed = function(cell)
+{
+ return this.model.isCollapsed(cell);
+};
+
+/**
+ * Function: isCellConnectable
+ *
+ * Returns true if the given cell is connectable in this graph. This
+ * implementation uses <mxGraphModel.isConnectable>. Subclassers can override
+ * this to implement specific connectable states for cells in only one graph,
+ * that is, without affecting the connectable state of the cell in the model.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraph.prototype.isCellConnectable = function(cell)
+{
+ return this.model.isConnectable(cell);
+};
+
+/**
+ * Function: isOrthogonal
+ *
+ * Returns true if perimeter points should be computed such that the
+ * resulting edge has only horizontal or vertical segments.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> that represents the edge.
+ */
+mxGraph.prototype.isOrthogonal = function(edge)
+{
+ var orthogonal = edge.style[mxConstants.STYLE_ORTHOGONAL];
+
+ if (orthogonal != null)
+ {
+ return orthogonal;
+ }
+
+ var tmp = this.view.getEdgeStyle(edge);
+
+ return tmp == mxEdgeStyle.SegmentConnector ||
+ tmp == mxEdgeStyle.ElbowConnector ||
+ tmp == mxEdgeStyle.SideToSide ||
+ tmp == mxEdgeStyle.TopToBottom ||
+ tmp == mxEdgeStyle.EntityRelation ||
+ tmp == mxEdgeStyle.OrthConnector;
+};
+
+/**
+ * Function: isLoop
+ *
+ * Returns true if the given cell state is a loop.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents a potential loop.
+ */
+mxGraph.prototype.isLoop = function(state)
+{
+ var src = state.getVisibleTerminalState(true);
+ var trg = state.getVisibleTerminalState(false);
+
+ return (src != null && src == trg);
+};
+
+/**
+ * Function: isCloneEvent
+ *
+ * Returns true if the given event is a clone event. This implementation
+ * returns true if control is pressed.
+ */
+mxGraph.prototype.isCloneEvent = function(evt)
+{
+ return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isToggleEvent
+ *
+ * Returns true if the given event is a toggle event. This implementation
+ * returns true if the meta key (Cmd) is pressed on Macs or if control is
+ * pressed on any other platform.
+ */
+mxGraph.prototype.isToggleEvent = function(evt)
+{
+ return (mxClient.IS_MAC) ? mxEvent.isMetaDown(evt) : mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isGridEnabledEvent
+ *
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isGridEnabledEvent = function(evt)
+{
+ return evt != null && !mxEvent.isAltDown(evt);
+};
+
+/**
+ * Function: isConstrainedEvent
+ *
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isConstrainedEvent = function(evt)
+{
+ return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isForceMarqueeEvent
+ *
+ * Returns true if the given event forces marquee selection. This implementation
+ * returns true if alt is pressed.
+ */
+mxGraph.prototype.isForceMarqueeEvent = function(evt)
+{
+ return mxEvent.isAltDown(evt);
+};
+
+/**
+ * Group: Validation
+ */
+
+/**
+ * Function: validationAlert
+ *
+ * Displays the given validation error in a dialog. This implementation uses
+ * mxUtils.alert.
+ */
+mxGraph.prototype.validationAlert = function(message)
+{
+ mxUtils.alert(message);
+};
+
+/**
+ * Function: isEdgeValid
+ *
+ * Checks if the return value of <getEdgeValidationError> for the given
+ * arguments is null.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.isEdgeValid = function(edge, source, target)
+{
+ return this.getEdgeValidationError(edge, source, target) == null;
+};
+
+/**
+ * Function: getEdgeValidationError
+ *
+ * Returns the validation error message to be displayed when inserting or
+ * changing an edges' connectivity. A return value of null means the edge
+ * is valid, a return value of '' means it's not valid, but do not display
+ * an error message. Any other (non-empty) string returned from this method
+ * is displayed as an error message when trying to connect an edge to a
+ * source and target. This implementation uses the <multiplicities>, and
+ * checks <multigraph>, <allowDanglingEdges> and <allowLoops> to generate
+ * validation errors.
+ *
+ * For extending this method with specific checks for source/target cells,
+ * the method can be extended as follows. Returning an empty string means
+ * the edge is invalid with no error message, a non-null string specifies
+ * the error message, and null means the edge is valid.
+ *
+ * (code)
+ * graph.getEdgeValidationError = function(edge, source, target)
+ * {
+ * if (source != null && target != null &&
+ * this.model.getValue(source) != null &&
+ * this.model.getValue(target) != null)
+ * {
+ * if (target is not valid for source)
+ * {
+ * return 'Invalid Target';
+ * }
+ * }
+ *
+ * // "Supercall"
+ * return mxGraph.prototype.getEdgeValidationError.apply(this, arguments);
+ * }
+ * (end)
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.getEdgeValidationError = function(edge, source, target)
+{
+ if (edge != null && !this.isAllowDanglingEdges() && (source == null || target == null))
+ {
+ return '';
+ }
+
+ if (edge != null && this.model.getTerminal(edge, true) == null &&
+ this.model.getTerminal(edge, false) == null)
+ {
+ return null;
+ }
+
+ // Checks if we're dealing with a loop
+ if (!this.allowLoops && source == target && source != null)
+ {
+ return '';
+ }
+
+ // Checks if the connection is generally allowed
+ if (!this.isValidConnection(source, target))
+ {
+ return '';
+ }
+
+ if (source != null && target != null)
+ {
+ var error = '';
+
+ // Checks if the cells are already connected
+ // and adds an error message if required
+ if (!this.multigraph)
+ {
+ var tmp = this.model.getEdgesBetween(source, target, true);
+
+ // Checks if the source and target are not connected by another edge
+ if (tmp.length > 1 || (tmp.length == 1 && tmp[0] != edge))
+ {
+ error += (mxResources.get(this.alreadyConnectedResource) ||
+ this.alreadyConnectedResource)+'\n';
+ }
+ }
+
+ // Gets the number of outgoing edges from the source
+ // and the number of incoming edges from the target
+ // without counting the edge being currently changed.
+ var sourceOut = this.model.getDirectedEdgeCount(source, true, edge);
+ var targetIn = this.model.getDirectedEdgeCount(target, false, edge);
+
+ // Checks the change against each multiplicity rule
+ if (this.multiplicities != null)
+ {
+ for (var i = 0; i < this.multiplicities.length; i++)
+ {
+ var err = this.multiplicities[i].check(this, edge, source,
+ target, sourceOut, targetIn);
+
+ if (err != null)
+ {
+ error += err;
+ }
+ }
+ }
+
+ // Validates the source and target terminals independently
+ var err = this.validateEdge(edge, source, target);
+
+ if (err != null)
+ {
+ error += err;
+ }
+
+ return (error.length > 0) ? error : null;
+ }
+
+ return (this.allowDanglingEdges) ? null : '';
+};
+
+/**
+ * Function: validateEdge
+ *
+ * Hook method for subclassers to return an error message for the given
+ * edge and terminals. This implementation returns null.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.validateEdge = function(edge, source, target)
+{
+ return null;
+};
+
+/**
+ * Function: validateGraph
+ *
+ * Validates the graph by validating each descendant of the given cell or
+ * the root of the model. Context is an object that contains the validation
+ * state for the complete validation run. The validation errors are
+ * attached to their cells using <setCellWarning>. This function returns true
+ * if no validation errors exist in the graph.
+ *
+ * Paramters:
+ *
+ * cell - Optional <mxCell> to start the validation recursion. Default is
+ * the graph root.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateGraph = function(cell, context)
+{
+ cell = (cell != null) ? cell : this.model.getRoot();
+ context = (context != null) ? context : new Object();
+
+ var isValid = true;
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var tmp = this.model.getChildAt(cell, i);
+ var ctx = context;
+
+ if (this.isValidRoot(tmp))
+ {
+ ctx = new Object();
+ }
+
+ var warn = this.validateGraph(tmp, ctx);
+
+ if (warn != null)
+ {
+ this.setCellWarning(tmp, warn.replace(/\n/g, '<br>'));
+ }
+ else
+ {
+ this.setCellWarning(tmp, null);
+ }
+
+ isValid = isValid && warn == null;
+ }
+
+ var warning = '';
+
+ // Adds error for invalid children if collapsed (children invisible)
+ if (this.isCellCollapsed(cell) && !isValid)
+ {
+ warning += (mxResources.get(this.containsValidationErrorsResource) ||
+ this.containsValidationErrorsResource)+'\n';
+ }
+
+ // Checks edges and cells using the defined multiplicities
+ if (this.model.isEdge(cell))
+ {
+ warning += this.getEdgeValidationError(cell,
+ this.model.getTerminal(cell, true),
+ this.model.getTerminal(cell, false)) || '';
+ }
+ else
+ {
+ warning += this.getCellValidationError(cell) || '';
+ }
+
+ // Checks custom validation rules
+ var err = this.validateCell(cell, context);
+
+ if (err != null)
+ {
+ warning += err;
+ }
+
+ // Updates the display with the warning icons
+ // before any potential alerts are displayed.
+ // LATER: Move this into addCellOverlay. Redraw
+ // should check if overlay was added or removed.
+ if (this.model.getParent(cell) == null)
+ {
+ this.view.validate();
+ }
+
+ return (warning.length > 0 || !isValid) ? warning : null;
+};
+
+/**
+ * Function: getCellValidationError
+ *
+ * Checks all <multiplicities> that cannot be enforced while the graph is
+ * being modified, namely, all multiplicities that require a minimum of
+ * 1 edge.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the multiplicities should be checked.
+ */
+mxGraph.prototype.getCellValidationError = function(cell)
+{
+ var outCount = this.model.getDirectedEdgeCount(cell, true);
+ var inCount = this.model.getDirectedEdgeCount(cell, false);
+ var value = this.model.getValue(cell);
+ var error = '';
+
+ if (this.multiplicities != null)
+ {
+ for (var i = 0; i < this.multiplicities.length; i++)
+ {
+ var rule = this.multiplicities[i];
+
+ if (rule.source && mxUtils.isNode(value, rule.type,
+ rule.attr, rule.value) && ((rule.max == 0 && outCount > 0) ||
+ (rule.min == 1 && outCount == 0) || (rule.max == 1 && outCount > 1)))
+ {
+ error += rule.countError + '\n';
+ }
+ else if (!rule.source && mxUtils.isNode(value, rule.type,
+ rule.attr, rule.value) && ((rule.max == 0 && inCount > 0) ||
+ (rule.min == 1 && inCount == 0) || (rule.max == 1 && inCount > 1)))
+ {
+ error += rule.countError + '\n';
+ }
+ }
+ }
+
+ return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: validateCell
+ *
+ * Hook method for subclassers to return an error message for the given
+ * cell and validation context. This implementation returns null. Any HTML
+ * breaks will be converted to linefeeds in the calling method.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the cell to validate.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateCell = function(cell, context)
+{
+ return null;
+};
+
+/**
+ * Group: Graph appearance
+ */
+
+/**
+ * Function: getBackgroundImage
+ *
+ * Returns the <backgroundImage> as an <mxImage>.
+ */
+mxGraph.prototype.getBackgroundImage = function()
+{
+ return this.backgroundImage;
+};
+
+/**
+ * Function: setBackgroundImage
+ *
+ * Sets the new <backgroundImage>.
+ *
+ * Parameters:
+ *
+ * image - New <mxImage> to be used for the background.
+ */
+mxGraph.prototype.setBackgroundImage = function(image)
+{
+ this.backgroundImage = image;
+};
+
+/**
+ * Function: getFoldingImage
+ *
+ * Returns the <mxImage> used to display the collapsed state of
+ * the specified cell state. This returns null for all edges.
+ */
+mxGraph.prototype.getFoldingImage = function(state)
+{
+ if (state != null && this.foldingEnabled && !this.getModel().isEdge(state.cell))
+ {
+ var tmp = this.isCellCollapsed(state.cell);
+
+ if (this.isCellFoldable(state.cell, !tmp))
+ {
+ return (tmp) ? this.collapsedImage : this.expandedImage;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: convertValueToString
+ *
+ * Returns the textual representation for the given cell. This
+ * implementation returns the nodename or string-representation of the user
+ * object.
+ *
+ * Example:
+ *
+ * The following returns the label attribute from the cells user
+ * object if it is an XML node.
+ *
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ * return cell.getAttribute('label');
+ * }
+ * (end)
+ *
+ * See also: <cellLabelChanged>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose textual representation should be returned.
+ */
+mxGraph.prototype.convertValueToString = function(cell)
+{
+ var value = this.model.getValue(cell);
+
+ if (value != null)
+ {
+ if (mxUtils.isNode(value))
+ {
+ return value.nodeName;
+ }
+ else if (typeof(value.toString) == 'function')
+ {
+ return value.toString();
+ }
+ }
+
+ return '';
+};
+
+/**
+ * Function: getLabel
+ *
+ * Returns a string or DOM node that represents the label for the given
+ * cell. This implementation uses <convertValueToString> if <labelsVisible>
+ * is true. Otherwise it returns an empty string.
+ *
+ * To truncate label to match the size of the cell, the following code
+ * can be used.
+ *
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ * var label = mxGraph.prototype.getLabel.apply(this, arguments);
+ *
+ * if (label != null && this.model.isVertex(cell))
+ * {
+ * var geo = this.getCellGeometry(cell);
+ *
+ * if (geo != null)
+ * {
+ * var max = parseInt(geo.width / 8);
+ *
+ * if (label.length > max)
+ * {
+ * label = label.substring(0, max)+'...';
+ * }
+ * }
+ * }
+ * return mxUtils.htmlEntities(label);
+ * }
+ * (end)
+ *
+ * A resize listener is needed in the graph to force a repaint of the label
+ * after a resize.
+ *
+ * (code)
+ * graph.addListener(mxEvent.RESIZE_CELLS, function(sender, evt)
+ * {
+ * var cells = evt.getProperty('cells');
+ *
+ * for (var i = 0; i < cells.length; i++)
+ * {
+ * this.view.removeState(cells[i]);
+ * }
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be returned.
+ */
+mxGraph.prototype.getLabel = function(cell)
+{
+ var result = '';
+
+ if (this.labelsVisible && cell != null)
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ if (!mxUtils.getValue(style, mxConstants.STYLE_NOLABEL, false))
+ {
+ result = this.convertValueToString(cell);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: isHtmlLabel
+ *
+ * Returns true if the label must be rendered as HTML markup. The default
+ * implementation returns <htmlLabels>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be displayed as HTML markup.
+ */
+mxGraph.prototype.isHtmlLabel = function(cell)
+{
+ return this.isHtmlLabels();
+};
+
+/**
+ * Function: isHtmlLabels
+ *
+ * Returns <htmlLabels>.
+ */
+mxGraph.prototype.isHtmlLabels = function()
+{
+ return this.htmlLabels;
+};
+
+/**
+ * Function: setHtmlLabels
+ *
+ * Sets <htmlLabels>.
+ */
+mxGraph.prototype.setHtmlLabels = function(value)
+{
+ this.htmlLabels = value;
+};
+
+/**
+ * Function: isWrapping
+ *
+ * This enables wrapping for HTML labels.
+ *
+ * Returns true if no white-space CSS style directive should be used for
+ * displaying the given cells label. This implementation returns true if
+ * <mxConstants.STYLE_WHITE_SPACE> in the style of the given cell is 'wrap'.
+ *
+ * This is used as a workaround for IE ignoring the white-space directive
+ * of child elements if the directive appears in a parent element. It
+ * should be overridden to return true if a white-space directive is used
+ * in the HTML markup that represents the given cells label. In order for
+ * HTML markup to work in labels, <isHtmlLabel> must also return true
+ * for the given cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ * var tmp = mxGraph.prototype.getLabel.apply(this, arguments); // "supercall"
+ *
+ * if (this.model.isEdge(cell))
+ * {
+ * tmp = '<div style="width: 150px; white-space:normal;">'+tmp+'</div>';
+ * }
+ *
+ * return tmp;
+ * }
+ *
+ * graph.isWrapping = function(state)
+ * {
+ * return this.model.isEdge(state.cell);
+ * }
+ * (end)
+ *
+ * Makes sure no edge label is wider than 150 pixels, otherwise the content
+ * is wrapped. Note: No width must be specified for wrapped vertex labels as
+ * the vertex defines the width in its geometry.
+ *
+ * Parameters:
+ *
+ * state - <mxCell> whose label should be wrapped.
+ */
+mxGraph.prototype.isWrapping = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return (style != null) ? style[mxConstants.STYLE_WHITE_SPACE] == 'wrap' : false;
+};
+
+/**
+ * Function: isLabelClipped
+ *
+ * Returns true if the overflow portion of labels should be hidden. If this
+ * returns true then vertex labels will be clipped to the size of the vertices.
+ * This implementation returns true if <mxConstants.STYLE_OVERFLOW> in the
+ * style of the given cell is 'hidden'.
+ *
+ * Parameters:
+ *
+ * state - <mxCell> whose label should be clipped.
+ */
+mxGraph.prototype.isLabelClipped = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return (style != null) ? style[mxConstants.STYLE_OVERFLOW] == 'hidden' : false;
+};
+
+/**
+ * Function: getTooltip
+ *
+ * Returns the string or DOM node that represents the tooltip for the given
+ * state, node and coordinate pair. This implementation checks if the given
+ * node is a folding icon or overlay and returns the respective tooltip. If
+ * this does not result in a tooltip, the handler for the cell is retrieved
+ * from <selectionCellsHandler> and the optional getTooltipForNode method is
+ * called. If no special tooltip exists here then <getTooltipForCell> is used
+ * with the cell in the given state as the argument to return a tooltip for the
+ * given state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose tooltip should be returned.
+ * node - DOM node that is currently under the mouse.
+ * x - X-coordinate of the mouse.
+ * y - Y-coordinate of the mouse.
+ */
+mxGraph.prototype.getTooltip = function(state, node, x, y)
+{
+ var tip = null;
+
+ if (state != null)
+ {
+ // Checks if the mouse is over the folding icon
+ if (state.control != null && (node == state.control.node ||
+ node.parentNode == state.control.node))
+ {
+ tip = this.collapseExpandResource;
+ tip = mxResources.get(tip) || tip;
+ }
+
+ if (tip == null && state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ // LATER: Exit loop if tip is not null
+ if (tip == null && (node == shape.node || node.parentNode == shape.node))
+ {
+ tip = shape.overlay.toString();
+ }
+ });
+ }
+
+ if (tip == null)
+ {
+ var handler = this.selectionCellsHandler.getHandler(state.cell);
+
+ if (handler != null && typeof(handler.getTooltipForNode) == 'function')
+ {
+ tip = handler.getTooltipForNode(node);
+ }
+ }
+
+ if (tip == null)
+ {
+ tip = this.getTooltipForCell(state.cell);
+ }
+ }
+
+ return tip;
+};
+
+/**
+ * Function: getTooltipForCell
+ *
+ * Returns the string or DOM node to be used as the tooltip for the given
+ * cell. This implementation uses the cells getTooltip function if it
+ * exists, or else it returns <convertValueToString> for the cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ * return 'Hello, World!';
+ * }
+ * (end)
+ *
+ * Replaces all tooltips with the string Hello, World!
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose tooltip should be returned.
+ */
+mxGraph.prototype.getTooltipForCell = function(cell)
+{
+ var tip = null;
+
+ if (cell != null && cell.getTooltip != null)
+ {
+ tip = cell.getTooltip();
+ }
+ else
+ {
+ tip = this.convertValueToString(cell);
+ }
+
+ return tip;
+};
+
+/**
+ * Function: getCursorForCell
+ *
+ * Returns the cursor value to be used for the CSS of the shape for the
+ * given cell. This implementation returns null.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose cursor should be returned.
+ */
+mxGraph.prototype.getCursorForCell = function(cell)
+{
+ return null;
+};
+
+/**
+ * Function: getStartSize
+ *
+ * Returns the start size of the given swimlane, that is, the width or
+ * height of the part that contains the title, depending on the
+ * horizontal style. The return value is an <mxRectangle> with either
+ * width or height set as appropriate.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> whose start size should be returned.
+ */
+mxGraph.prototype.getStartSize = function(swimlane)
+{
+ var result = new mxRectangle();
+ var state = this.view.getState(swimlane);
+ var style = (state != null) ? state.style : this.getCellStyle(swimlane);
+
+ if (style != null)
+ {
+ var size = parseInt(mxUtils.getValue(style,
+ mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+
+ if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ result.height = size;
+ }
+ else
+ {
+ result.width = size;
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getImage
+ *
+ * Returns the image URL for the given cell state. This implementation
+ * returns the value stored under <mxConstants.STYLE_IMAGE> in the cell
+ * style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose image URL should be returned.
+ */
+mxGraph.prototype.getImage = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_IMAGE] : null;
+};
+
+/**
+ * Function: getVerticalAlign
+ *
+ * Returns the vertical alignment for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_VERTICAL_ALIGN> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose vertical alignment should be
+ * returned.
+ */
+mxGraph.prototype.getVerticalAlign = function(state)
+{
+ return (state != null && state.style != null) ?
+ (state.style[mxConstants.STYLE_VERTICAL_ALIGN] ||
+ mxConstants.ALIGN_MIDDLE ):
+ null;
+};
+
+/**
+ * Function: getIndicatorColor
+ *
+ * Returns the indicator color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_COLOR> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorColor = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_COLOR] : null;
+};
+
+/**
+ * Function: getIndicatorGradientColor
+ *
+ * Returns the indicator gradient color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_GRADIENTCOLOR> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator gradient color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorGradientColor = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR] : null;
+};
+
+/**
+ * Function: getIndicatorShape
+ *
+ * Returns the indicator shape for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_SHAPE> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator shape should be returned.
+ */
+mxGraph.prototype.getIndicatorShape = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_SHAPE] : null;
+};
+
+/**
+ * Function: getIndicatorImage
+ *
+ * Returns the indicator image for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_IMAGE> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator image should be returned.
+ */
+mxGraph.prototype.getIndicatorImage = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_IMAGE] : null;
+};
+
+/**
+ * Function: getBorder
+ *
+ * Returns the value of <border>.
+ */
+mxGraph.prototype.getBorder = function()
+{
+ return this.border;
+};
+
+/**
+ * Function: setBorder
+ *
+ * Sets the value of <border>.
+ *
+ * Parameters:
+ *
+ * value - Positive integer that represents the border to be used.
+ */
+mxGraph.prototype.setBorder = function(value)
+{
+ this.border = value;
+};
+
+/**
+ * Function: isSwimlane
+ *
+ * Returns true if the given cell is a swimlane in the graph. A swimlane is
+ * a container cell with some specific behaviour. This implementation
+ * checks if the shape associated with the given cell is a <mxSwimlane>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be checked.
+ */
+mxGraph.prototype.isSwimlane = function (cell)
+{
+ if (cell != null)
+ {
+ if (this.model.getParent(cell) != this.model.getRoot())
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ if (style != null && !this.model.isEdge(cell))
+ {
+ return style[mxConstants.STYLE_SHAPE] ==
+ mxConstants.SHAPE_SWIMLANE;
+ }
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Group: Graph behaviour
+ */
+
+/**
+ * Function: isResizeContainer
+ *
+ * Returns <resizeContainer>.
+ */
+mxGraph.prototype.isResizeContainer = function()
+{
+ return this.resizeContainer;
+};
+
+/**
+ * Function: setResizeContainer
+ *
+ * Sets <resizeContainer>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the container should be resized.
+ */
+mxGraph.prototype.setResizeContainer = function(value)
+{
+ this.resizeContainer = value;
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if the graph is <enabled>.
+ */
+mxGraph.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Specifies if the graph should allow any interactions. This
+ * implementation updates <enabled>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should be enabled.
+ */
+mxGraph.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isEscapeEnabled
+ *
+ * Returns <escapeEnabled>.
+ */
+mxGraph.prototype.isEscapeEnabled = function()
+{
+ return this.escapeEnabled;
+};
+
+/**
+ * Function: setEscapeEnabled
+ *
+ * Sets <escapeEnabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean indicating if escape should be enabled.
+ */
+mxGraph.prototype.setEscapeEnabled = function(value)
+{
+ this.escapeEnabled = value;
+};
+
+/**
+ * Function: isInvokesStopCellEditing
+ *
+ * Returns <invokesStopCellEditing>.
+ */
+mxGraph.prototype.isInvokesStopCellEditing = function()
+{
+ return this.invokesStopCellEditing;
+};
+
+/**
+ * Function: setInvokesStopCellEditing
+ *
+ * Sets <invokesStopCellEditing>.
+ */
+mxGraph.prototype.setInvokesStopCellEditing = function(value)
+{
+ this.invokesStopCellEditing = value;
+};
+
+/**
+ * Function: isEnterStopsCellEditing
+ *
+ * Returns <enterStopsCellEditing>.
+ */
+mxGraph.prototype.isEnterStopsCellEditing = function()
+{
+ return this.enterStopsCellEditing;
+};
+
+/**
+ * Function: setEnterStopsCellEditing
+ *
+ * Sets <enterStopsCellEditing>.
+ */
+mxGraph.prototype.setEnterStopsCellEditing = function(value)
+{
+ this.enterStopsCellEditing = value;
+};
+
+/**
+ * Function: isCellLocked
+ *
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellLocked = function(cell)
+{
+ var geometry = this.model.getGeometry(cell);
+
+ return this.isCellsLocked() || (geometry != null &&
+ this.model.isVertex(cell) && geometry.relative);
+};
+
+/**
+ * Function: isCellsLocked
+ *
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellsLocked = function()
+{
+ return this.cellsLocked;
+};
+
+/**
+ * Function: setLocked
+ *
+ * Sets if any cell may be moved, sized, bended, disconnected, edited or
+ * selected.
+ *
+ * Parameters:
+ *
+ * value - Boolean that defines the new value for <cellsLocked>.
+ */
+mxGraph.prototype.setCellsLocked = function(value)
+{
+ this.cellsLocked = value;
+};
+
+/**
+ * Function: getCloneableCells
+ *
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getCloneableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellCloneable(cell);
+ }));
+};
+
+/**
+ * Function: isCellCloneable
+ *
+ * Returns true if the given cell is cloneable. This implementation returns
+ * <isCellsCloneable> for all cells unless a cell style specifies
+ * <mxConstants.STYLE_CLONEABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> whose cloneable state should be returned.
+ */
+mxGraph.prototype.isCellCloneable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsCloneable() && style[mxConstants.STYLE_CLONEABLE] != 0;
+};
+
+/**
+ * Function: isCellsCloneable
+ *
+ * Returns <cellsCloneable>, that is, if the graph allows cloning of cells
+ * by using control-drag.
+ */
+mxGraph.prototype.isCellsCloneable = function()
+{
+ return this.cellsCloneable;
+};
+
+/**
+ * Function: setCellsCloneable
+ *
+ * Specifies if the graph should allow cloning of cells by holding down the
+ * control key while cells are being moved. This implementation updates
+ * <cellsCloneable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should be cloneable.
+ */
+mxGraph.prototype.setCellsCloneable = function(value)
+{
+ this.cellsCloneable = value;
+};
+
+/**
+ * Function: getExportableCells
+ *
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getExportableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.canExportCell(cell);
+ }));
+};
+
+/**
+ * Function: canExportCell
+ *
+ * Returns true if the given cell may be exported to the clipboard. This
+ * implementation returns <exportEnabled> for all cells.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the cell to be exported.
+ */
+mxGraph.prototype.canExportCell = function(cell)
+{
+ return this.exportEnabled;
+};
+
+/**
+ * Function: getImportableCells
+ *
+ * Returns the cells which may be imported in the given array of cells.
+ */
+mxGraph.prototype.getImportableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.canImportCell(cell);
+ }));
+};
+
+/**
+ * Function: canImportCell
+ *
+ * Returns true if the given cell may be imported from the clipboard.
+ * This implementation returns <importEnabled> for all cells.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the cell to be imported.
+ */
+mxGraph.prototype.canImportCell = function(cell)
+{
+ return this.importEnabled;
+};
+
+/**
+ * Function: isCellSelectable
+ *
+ * Returns true if the given cell is selectable. This implementation
+ * returns <cellsSelectable>.
+ *
+ * To add a new style for making cells (un)selectable, use the following code.
+ *
+ * (code)
+ * mxGraph.prototype.isCellSelectable = function(cell)
+ * {
+ * var state = this.view.getState(cell);
+ * var style = (state != null) ? state.style : this.getCellStyle(cell);
+ *
+ * return this.isCellsSelectable() && !this.isCellLocked(cell) && style['selectable'] != 0;
+ * };
+ * (end)
+ *
+ * You can then use the new style as shown in this example.
+ *
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'selectable=0');
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose selectable state should be returned.
+ */
+mxGraph.prototype.isCellSelectable = function(cell)
+{
+ return this.isCellsSelectable();
+};
+
+/**
+ * Function: isCellsSelectable
+ *
+ * Returns <cellsSelectable>.
+ */
+mxGraph.prototype.isCellsSelectable = function()
+{
+ return this.cellsSelectable;
+};
+
+/**
+ * Function: setCellsSelectable
+ *
+ * Sets <cellsSelectable>.
+ */
+mxGraph.prototype.setCellsSelectable = function(value)
+{
+ this.cellsSelectable = value;
+};
+
+/**
+ * Function: getDeletableCells
+ *
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getDeletableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellDeletable(cell);
+ }));
+};
+
+/**
+ * Function: isCellDeletable
+ *
+ * Returns true if the given cell is moveable. This returns
+ * <cellsDeletable> for all given cells if a cells style does not specify
+ * <mxConstants.STYLE_DELETABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose deletable state should be returned.
+ */
+mxGraph.prototype.isCellDeletable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsDeletable() && style[mxConstants.STYLE_DELETABLE] != 0;
+};
+
+/**
+ * Function: isCellsDeletable
+ *
+ * Returns <cellsDeletable>.
+ */
+mxGraph.prototype.isCellsDeletable = function()
+{
+ return this.cellsDeletable;
+};
+
+/**
+ * Function: setCellsDeletable
+ *
+ * Sets <cellsDeletable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow deletion of cells.
+ */
+mxGraph.prototype.setCellsDeletable = function(value)
+{
+ this.cellsDeletable = value;
+};
+
+/**
+ * Function: isLabelMovable
+ *
+ * Returns true if the given edges's label is moveable. This returns
+ * <movable> for all given cells if <isLocked> does not return true
+ * for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be moved.
+ */
+mxGraph.prototype.isLabelMovable = function(cell)
+{
+ return !this.isCellLocked(cell) &&
+ ((this.model.isEdge(cell) && this.edgeLabelsMovable) ||
+ (this.model.isVertex(cell) && this.vertexLabelsMovable));
+};
+
+/**
+ * Function: getMovableCells
+ *
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getMovableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellMovable(cell);
+ }));
+};
+
+/**
+ * Function: isCellMovable
+ *
+ * Returns true if the given cell is moveable. This returns <cellsMovable>
+ * for all given cells if <isCellLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_MOVABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraph.prototype.isCellMovable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsMovable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_MOVABLE] != 0;
+};
+
+/**
+ * Function: isCellsMovable
+ *
+ * Returns <cellsMovable>.
+ */
+mxGraph.prototype.isCellsMovable = function()
+{
+ return this.cellsMovable;
+};
+
+/**
+ * Function: setCellsMovable
+ *
+ * Specifies if the graph should allow moving of cells. This implementation
+ * updates <cellsMsovable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow moving of cells.
+ */
+mxGraph.prototype.setCellsMovable = function(value)
+{
+ this.cellsMovable = value;
+};
+
+/**
+ * Function: isGridEnabled
+ *
+ * Returns <gridEnabled> as a boolean.
+ */
+mxGraph.prototype.isGridEnabled = function()
+{
+ return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ *
+ * Specifies if the grid should be enabled.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the grid should be enabled.
+ */
+mxGraph.prototype.setGridEnabled = function(value)
+{
+ this.gridEnabled = value;
+};
+
+/**
+ * Function: isPortsEnabled
+ *
+ * Returns <portsEnabled> as a boolean.
+ */
+mxGraph.prototype.isPortsEnabled = function()
+{
+ return this.portsEnabled;
+};
+
+/**
+ * Function: setPortsEnabled
+ *
+ * Specifies if the ports should be enabled.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the ports should be enabled.
+ */
+mxGraph.prototype.setPortsEnabled = function(value)
+{
+ this.portsEnabled = value;
+};
+
+/**
+ * Function: getGridSize
+ *
+ * Returns <gridSize>.
+ */
+mxGraph.prototype.getGridSize = function()
+{
+ return this.gridSize;
+};
+
+/**
+ * Function: setGridSize
+ *
+ * Sets <gridSize>.
+ */
+mxGraph.prototype.setGridSize = function(value)
+{
+ this.gridSize = value;
+};
+
+/**
+ * Function: getTolerance
+ *
+ * Returns <tolerance>.
+ */
+mxGraph.prototype.getTolerance = function()
+{
+ return this.tolerance;
+};
+
+/**
+ * Function: setTolerance
+ *
+ * Sets <tolerance>.
+ */
+mxGraph.prototype.setTolerance = function(value)
+{
+ this.tolerance = value;
+};
+
+/**
+ * Function: isVertexLabelsMovable
+ *
+ * Returns <vertexLabelsMovable>.
+ */
+mxGraph.prototype.isVertexLabelsMovable = function()
+{
+ return this.vertexLabelsMovable;
+};
+
+/**
+ * Function: setVertexLabelsMovable
+ *
+ * Sets <vertexLabelsMovable>.
+ */
+mxGraph.prototype.setVertexLabelsMovable = function(value)
+{
+ this.vertexLabelsMovable = value;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ *
+ * Returns <edgeLabelsMovable>.
+ */
+mxGraph.prototype.isEdgeLabelsMovable = function()
+{
+ return this.edgeLabelsMovable;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ *
+ * Sets <edgeLabelsMovable>.
+ */
+mxGraph.prototype.setEdgeLabelsMovable = function(value)
+{
+ this.edgeLabelsMovable = value;
+};
+
+/**
+ * Function: isSwimlaneNesting
+ *
+ * Returns <swimlaneNesting> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneNesting = function()
+{
+ return this.swimlaneNesting;
+};
+
+/**
+ * Function: setSwimlaneNesting
+ *
+ * Specifies if swimlanes can be nested by drag and drop. This is only
+ * taken into account if dropEnabled is true.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if swimlanes can be nested.
+ */
+mxGraph.prototype.setSwimlaneNesting = function(value)
+{
+ this.swimlaneNesting = value;
+};
+
+/**
+ * Function: isSwimlaneSelectionEnabled
+ *
+ * Returns <swimlaneSelectionEnabled> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneSelectionEnabled = function()
+{
+ return this.swimlaneSelectionEnabled;
+};
+
+/**
+ * Function: setSwimlaneSelectionEnabled
+ *
+ * Specifies if swimlanes should be selected if the mouse is released
+ * over their content area.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if swimlanes content areas
+ * should be selected when the mouse is released over them.
+ */
+mxGraph.prototype.setSwimlaneSelectionEnabled = function(value)
+{
+ this.swimlaneSelectionEnabled = value;
+};
+
+/**
+ * Function: isMultigraph
+ *
+ * Returns <multigraph> as a boolean.
+ */
+mxGraph.prototype.isMultigraph = function()
+{
+ return this.multigraph;
+};
+
+/**
+ * Function: setMultigraph
+ *
+ * Specifies if the graph should allow multiple connections between the
+ * same pair of vertices.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph allows multiple connections
+ * between the same pair of vertices.
+ */
+mxGraph.prototype.setMultigraph = function(value)
+{
+ this.multigraph = value;
+};
+
+/**
+ * Function: isAllowLoops
+ *
+ * Returns <allowLoops> as a boolean.
+ */
+mxGraph.prototype.isAllowLoops = function()
+{
+ return this.allowLoops;
+};
+
+/**
+ * Function: setAllowDanglingEdges
+ *
+ * Specifies if dangling edges are allowed, that is, if edges are allowed
+ * that do not have a source and/or target terminal defined.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if dangling edges are allowed.
+ */
+mxGraph.prototype.setAllowDanglingEdges = function(value)
+{
+ this.allowDanglingEdges = value;
+};
+
+/**
+ * Function: isAllowDanglingEdges
+ *
+ * Returns <allowDanglingEdges> as a boolean.
+ */
+mxGraph.prototype.isAllowDanglingEdges = function()
+{
+ return this.allowDanglingEdges;
+};
+
+/**
+ * Function: setConnectableEdges
+ *
+ * Specifies if edges should be connectable.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if edges should be connectable.
+ */
+mxGraph.prototype.setConnectableEdges = function(value)
+{
+ this.connectableEdges = value;
+};
+
+/**
+ * Function: isConnectableEdges
+ *
+ * Returns <connectableEdges> as a boolean.
+ */
+mxGraph.prototype.isConnectableEdges = function()
+{
+ return this.connectableEdges;
+};
+
+/**
+ * Function: setCloneInvalidEdges
+ *
+ * Specifies if edges should be inserted when cloned but not valid wrt.
+ * <getEdgeValidationError>. If false such edges will be silently ignored.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if cloned invalid edges should be
+ * inserted into the graph or ignored.
+ */
+mxGraph.prototype.setCloneInvalidEdges = function(value)
+{
+ this.cloneInvalidEdges = value;
+};
+
+/**
+ * Function: isCloneInvalidEdges
+ *
+ * Returns <cloneInvalidEdges> as a boolean.
+ */
+mxGraph.prototype.isCloneInvalidEdges = function()
+{
+ return this.cloneInvalidEdges;
+};
+
+/**
+ * Function: setAllowLoops
+ *
+ * Specifies if loops are allowed.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if loops are allowed.
+ */
+mxGraph.prototype.setAllowLoops = function(value)
+{
+ this.allowLoops = value;
+};
+
+/**
+ * Function: isDisconnectOnMove
+ *
+ * Returns <disconnectOnMove> as a boolean.
+ */
+mxGraph.prototype.isDisconnectOnMove = function()
+{
+ return this.disconnectOnMove;
+};
+
+/**
+ * Function: setDisconnectOnMove
+ *
+ * Specifies if edges should be disconnected when moved. (Note: Cloned
+ * edges are always disconnected.)
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if edges should be disconnected
+ * when moved.
+ */
+mxGraph.prototype.setDisconnectOnMove = function(value)
+{
+ this.disconnectOnMove = value;
+};
+
+/**
+ * Function: isDropEnabled
+ *
+ * Returns <dropEnabled> as a boolean.
+ */
+mxGraph.prototype.isDropEnabled = function()
+{
+ return this.dropEnabled;
+};
+
+/**
+ * Function: setDropEnabled
+ *
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ *
+ * Parameters:
+ *
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setDropEnabled = function(value)
+{
+ this.dropEnabled = value;
+};
+
+/**
+ * Function: isSplitEnabled
+ *
+ * Returns <splitEnabled> as a boolean.
+ */
+mxGraph.prototype.isSplitEnabled = function()
+{
+ return this.splitEnabled;
+};
+
+/**
+ * Function: setSplitEnabled
+ *
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ *
+ * Parameters:
+ *
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setSplitEnabled = function(value)
+{
+ this.splitEnabled = value;
+};
+
+/**
+ * Function: isCellResizable
+ *
+ * Returns true if the given cell is resizable. This returns
+ * <cellsResizable> for all given cells if <isCellLocked> does not return
+ * true for the given cell and its style does not specify
+ * <mxConstants.STYLE_RESIZABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose resizable state should be returned.
+ */
+mxGraph.prototype.isCellResizable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsResizable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_RESIZABLE] != 0;
+};
+
+/**
+ * Function: isCellsResizable
+ *
+ * Returns <cellsResizable>.
+ */
+mxGraph.prototype.isCellsResizable = function()
+{
+ return this.cellsResizable;
+};
+
+/**
+ * Function: setCellsResizable
+ *
+ * Specifies if the graph should allow resizing of cells. This
+ * implementation updates <cellsResizable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow resizing of
+ * cells.
+ */
+mxGraph.prototype.setCellsResizable = function(value)
+{
+ this.cellsResizable = value;
+};
+
+/**
+ * Function: isTerminalPointMovable
+ *
+ * Returns true if the given terminal point is movable. This is independent
+ * from <isCellConnectable> and <isCellDisconnectable> and controls if terminal
+ * points can be moved in the graph if the edge is not connected. Note that it
+ * is required for this to return true to connect unconnected edges. This
+ * implementation returns true.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose terminal point should be moved.
+ * source - Boolean indicating if the source or target terminal should be moved.
+ */
+mxGraph.prototype.isTerminalPointMovable = function(cell, source)
+{
+ return true;
+};
+
+/**
+ * Function: isCellBendable
+ *
+ * Returns true if the given cell is bendable. This returns <cellsBendable>
+ * for all given cells if <isLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_BENDABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose bendable state should be returned.
+ */
+mxGraph.prototype.isCellBendable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsBendable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_BENDABLE] != 0;
+};
+
+/**
+ * Function: isCellsBendable
+ *
+ * Returns <cellsBenadable>.
+ */
+mxGraph.prototype.isCellsBendable = function()
+{
+ return this.cellsBendable;
+};
+
+/**
+ * Function: setCellsBendable
+ *
+ * Specifies if the graph should allow bending of edges. This
+ * implementation updates <bendable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow bending of
+ * edges.
+ */
+mxGraph.prototype.setCellsBendable = function(value)
+{
+ this.cellsBendable = value;
+};
+
+/**
+ * Function: isCellEditable
+ *
+ * Returns true if the given cell is editable. This returns <cellsEditable> for
+ * all given cells if <isCellLocked> does not return true for the given cell
+ * and its style does not specify <mxConstants.STYLE_EDITABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose editable state should be returned.
+ */
+mxGraph.prototype.isCellEditable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsEditable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_EDITABLE] != 0;
+};
+
+/**
+ * Function: isCellsEditable
+ *
+ * Returns <cellsEditable>.
+ */
+mxGraph.prototype.isCellsEditable = function()
+{
+ return this.cellsEditable;
+};
+
+/**
+ * Function: setCellsEditable
+ *
+ * Specifies if the graph should allow in-place editing for cell labels.
+ * This implementation updates <cellsEditable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow in-place
+ * editing.
+ */
+mxGraph.prototype.setCellsEditable = function(value)
+{
+ this.cellsEditable = value;
+};
+
+/**
+ * Function: isCellDisconnectable
+ *
+ * Returns true if the given cell is disconnectable from the source or
+ * target terminal. This returns <isCellsDisconnectable> for all given
+ * cells if <isCellLocked> does not return true for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose disconnectable state should be returned.
+ * terminal - <mxCell> that represents the source or target terminal.
+ * source - Boolean indicating if the source or target terminal is to be
+ * disconnected.
+ */
+mxGraph.prototype.isCellDisconnectable = function(cell, terminal, source)
+{
+ return this.isCellsDisconnectable() && !this.isCellLocked(cell);
+};
+
+/**
+ * Function: isCellsDisconnectable
+ *
+ * Returns <cellsDisconnectable>.
+ */
+mxGraph.prototype.isCellsDisconnectable = function()
+{
+ return this.cellsDisconnectable;
+};
+
+/**
+ * Function: setCellsDisconnectable
+ *
+ * Sets <cellsDisconnectable>.
+ */
+mxGraph.prototype.setCellsDisconnectable = function(value)
+{
+ this.cellsDisconnectable = value;
+};
+
+/**
+ * Function: isValidSource
+ *
+ * Returns true if the given cell is a valid source for new connections.
+ * This implementation returns true for all non-null values and is
+ * called by is called by <isValidConnection>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents a possible source or null.
+ */
+mxGraph.prototype.isValidSource = function(cell)
+{
+ return (cell == null && this.allowDanglingEdges) ||
+ (cell != null && (!this.model.isEdge(cell) ||
+ this.connectableEdges) && this.isCellConnectable(cell));
+};
+
+/**
+ * Function: isValidTarget
+ *
+ * Returns <isValidSource> for the given cell. This is called by
+ * <isValidConnection>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents a possible target or null.
+ */
+mxGraph.prototype.isValidTarget = function(cell)
+{
+ return this.isValidSource(cell);
+};
+
+/**
+ * Function: isValidConnection
+ *
+ * Returns true if the given target cell is a valid target for source.
+ * This is a boolean implementation for not allowing connections between
+ * certain pairs of vertices and is called by <getEdgeValidationError>.
+ * This implementation returns true if <isValidSource> returns true for
+ * the source and <isValidTarget> returns true for the target.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that represents the source cell.
+ * target - <mxCell> that represents the target cell.
+ */
+mxGraph.prototype.isValidConnection = function(source, target)
+{
+ return this.isValidSource(source) && this.isValidTarget(target);
+};
+
+/**
+ * Function: setConnectable
+ *
+ * Specifies if the graph should allow new connections. This implementation
+ * updates <mxConnectionHandler.enabled> in <connectionHandler>.
+ *
+ * Parameters:
+ *
+ * connectable - Boolean indicating if new connections should be allowed.
+ */
+mxGraph.prototype.setConnectable = function(connectable)
+{
+ this.connectionHandler.setEnabled(connectable);
+};
+
+/**
+ * Function: isConnectable
+ *
+ * Returns true if the <connectionHandler> is enabled.
+ */
+mxGraph.prototype.isConnectable = function(connectable)
+{
+ return this.connectionHandler.isEnabled();
+};
+
+/**
+ * Function: setTooltips
+ *
+ * Specifies if tooltips should be enabled. This implementation updates
+ * <mxTooltipHandler.enabled> in <tooltipHandler>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean indicating if tooltips should be enabled.
+ */
+mxGraph.prototype.setTooltips = function (enabled)
+{
+ this.tooltipHandler.setEnabled(enabled);
+};
+
+/**
+ * Function: setPanning
+ *
+ * Specifies if panning should be enabled. This implementation updates
+ * <mxPanningHandler.panningEnabled> in <panningHandler>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean indicating if panning should be enabled.
+ */
+mxGraph.prototype.setPanning = function(enabled)
+{
+ this.panningHandler.panningEnabled = enabled;
+};
+
+/**
+ * Function: isEditing
+ *
+ * Returns true if the given cell is currently being edited.
+ * If no cell is specified then this returns true if any
+ * cell is currently being edited.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be checked.
+ */
+mxGraph.prototype.isEditing = function(cell)
+{
+ if (this.cellEditor != null)
+ {
+ var editingCell = this.cellEditor.getEditingCell();
+
+ return (cell == null) ?
+ editingCell != null :
+ cell == editingCell;
+ }
+
+ return false;
+};
+
+/**
+ * Function: isAutoSizeCell
+ *
+ * Returns true if the size of the given cell should automatically be
+ * updated after a change of the label. This implementation returns
+ * <autoSizeCells> or checks if the cell style does specify
+ * <mxConstants.STYLE_AUTOSIZE> to be 1.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be resized.
+ */
+mxGraph.prototype.isAutoSizeCell = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isAutoSizeCells() || style[mxConstants.STYLE_AUTOSIZE] == 1;
+};
+
+/**
+ * Function: isAutoSizeCells
+ *
+ * Returns <autoSizeCells>.
+ */
+mxGraph.prototype.isAutoSizeCells = function()
+{
+ return this.autoSizeCells;
+};
+
+/**
+ * Function: setAutoSizeCells
+ *
+ * Specifies if cell sizes should be automatically updated after a label
+ * change. This implementation sets <autoSizeCells> to the given parameter.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if cells should be resized
+ * automatically.
+ */
+mxGraph.prototype.setAutoSizeCells = function(value)
+{
+ this.autoSizeCells = value;
+};
+
+/**
+ * Function: isExtendParent
+ *
+ * Returns true if the parent of the given cell should be extended if the
+ * child has been resized so that it overlaps the parent. This
+ * implementation returns <isExtendParents> if the cell is not an edge.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.isExtendParent = function(cell)
+{
+ return !this.getModel().isEdge(cell) && this.isExtendParents();
+};
+
+/**
+ * Function: isExtendParents
+ *
+ * Returns <extendParents>.
+ */
+mxGraph.prototype.isExtendParents = function()
+{
+ return this.extendParents;
+};
+
+/**
+ * Function: setExtendParents
+ *
+ * Sets <extendParents>.
+ *
+ * Parameters:
+ *
+ * value - New boolean value for <extendParents>.
+ */
+mxGraph.prototype.setExtendParents = function(value)
+{
+ this.extendParents = value;
+};
+
+/**
+ * Function: isExtendParentsOnAdd
+ *
+ * Returns <extendParentsOnAdd>.
+ */
+mxGraph.prototype.isExtendParentsOnAdd = function()
+{
+ return this.extendParentsOnAdd;
+};
+
+/**
+ * Function: setExtendParentsOnAdd
+ *
+ * Sets <extendParentsOnAdd>.
+ *
+ * Parameters:
+ *
+ * value - New boolean value for <extendParentsOnAdd>.
+ */
+mxGraph.prototype.setExtendParentsOnAdd = function(value)
+{
+ this.extendParentsOnAdd = value;
+};
+
+/**
+ * Function: isConstrainChild
+ *
+ * Returns true if the given cell should be kept inside the bounds of its
+ * parent according to the rules defined by <getOverlap> and
+ * <isAllowOverlapParent>. This implementation returns false for all children
+ * of edges and <isConstrainChildren> otherwise.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be constrained.
+ */
+mxGraph.prototype.isConstrainChild = function(cell)
+{
+ return this.isConstrainChildren() && !this.getModel().isEdge(this.getModel().getParent(cell));
+
+};
+
+/**
+ * Function: isConstrainChildren
+ *
+ * Returns <constrainChildren>.
+ */
+mxGraph.prototype.isConstrainChildren = function()
+{
+ return this.constrainChildren;
+};
+
+/**
+ * Function: setConstrainChildren
+ *
+ * Sets <constrainChildren>.
+ */
+mxGraph.prototype.setConstrainChildren = function(value)
+{
+ this.constrainChildren = value;
+};
+
+/**
+ * Function: isConstrainChildren
+ *
+ * Returns <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.isAllowNegativeCoordinates = function()
+{
+ return this.allowNegativeCoordinates;
+};
+
+/**
+ * Function: setConstrainChildren
+ *
+ * Sets <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.setAllowNegativeCoordinates = function(value)
+{
+ this.allowNegativeCoordinates = value;
+};
+
+/**
+ * Function: getOverlap
+ *
+ * Returns a decimal number representing the amount of the width and height
+ * of the given cell that is allowed to overlap its parent. A value of 0
+ * means all children must stay inside the parent, 1 means the child is
+ * allowed to be placed outside of the parent such that it touches one of
+ * the parents sides. If <isAllowOverlapParent> returns false for the given
+ * cell, then this method returns 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the overlap ratio should be returned.
+ */
+mxGraph.prototype.getOverlap = function(cell)
+{
+ return (this.isAllowOverlapParent(cell)) ? this.defaultOverlap : 0;
+};
+
+/**
+ * Function: isAllowOverlapParent
+ *
+ * Returns true if the given cell is allowed to be placed outside of the
+ * parents area.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the child to be checked.
+ */
+mxGraph.prototype.isAllowOverlapParent = function(cell)
+{
+ return false;
+};
+
+/**
+ * Function: getFoldableCells
+ *
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getFoldableCells = function(cells, collapse)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellFoldable(cell, collapse);
+ }));
+};
+
+/**
+ * Function: isCellFoldable
+ *
+ * Returns true if the given cell is foldable. This implementation
+ * returns true if the cell has at least one child and its style
+ * does not specify <mxConstants.STYLE_FOLDABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose foldable state should be returned.
+ */
+mxGraph.prototype.isCellFoldable = function(cell, collapse)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.model.getChildCount(cell) > 0 && style[mxConstants.STYLE_FOLDABLE] != 0;
+};
+
+/**
+ * Function: isValidDropTarget
+ *
+ * Returns true if the given cell is a valid drop target for the specified
+ * cells. If the given cell is an edge, then <isSplitDropTarget> is used,
+ * else <isParentDropTarget> is used to compute the return value.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible drop target.
+ * cells - <mxCells> that should be dropped into the target.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isValidDropTarget = function(cell, cells, evt)
+{
+ return cell != null && ((this.isSplitEnabled() &&
+ this.isSplitTarget(cell, cells, evt)) || (!this.model.isEdge(cell) &&
+ (this.isSwimlane(cell) || (this.model.getChildCount(cell) > 0 &&
+ !this.isCellCollapsed(cell)))));
+};
+
+/**
+ * Function: isSplitTarget
+ *
+ * Returns true if the given edge may be splitted into two edges with the
+ * given cell as a new terminal between the two.
+ *
+ * Parameters:
+ *
+ * target - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that should split the edge.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isSplitTarget = function(target, cells, evt)
+{
+ if (this.model.isEdge(target) && cells != null && cells.length == 1 &&
+ this.isCellConnectable(cells[0]) && this.getEdgeValidationError(target,
+ this.model.getTerminal(target, true), cells[0]) == null)
+ {
+ var src = this.model.getTerminal(target, true);
+ var trg = this.model.getTerminal(target, false);
+
+ return (!this.model.isAncestor(cells[0], src) &&
+ !this.model.isAncestor(cells[0], trg));
+ }
+
+ return false;
+};
+
+/**
+ * Function: getDropTarget
+ *
+ * Returns the given cell if it is a drop target for the given cells or the
+ * nearest ancestor that may be used as a drop target for the given cells.
+ * If the given array contains a swimlane and <swimlaneNesting> is false
+ * then this always returns null. If no cell is given, then the bottommost
+ * swimlane at the location of the given event is returned.
+ *
+ * This function should only be used if <isDropEnabled> returns true.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> which are to be dropped onto the target.
+ * evt - Mouseevent for the drag and drop.
+ * cell - <mxCell> that is under the mousepointer.
+ */
+mxGraph.prototype.getDropTarget = function(cells, evt, cell)
+{
+ if (!this.isSwimlaneNesting())
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.isSwimlane(cells[i]))
+ {
+ return null;
+ }
+ }
+ }
+
+ var pt = mxUtils.convertPoint(this.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ pt.x -= this.panDx;
+ pt.y -= this.panDy;
+ var swimlane = this.getSwimlaneAt(pt.x, pt.y);
+
+ if (cell == null)
+ {
+ cell = swimlane;
+ }
+ else if (swimlane != null)
+ {
+ // Checks if the cell is an ancestor of the swimlane
+ // under the mouse and uses the swimlane in that case
+ var tmp = this.model.getParent(swimlane);
+
+ while (tmp != null && this.isSwimlane(tmp) && tmp != cell)
+ {
+ tmp = this.model.getParent(tmp);
+ }
+
+ if (tmp == cell)
+ {
+ cell = swimlane;
+ }
+ }
+
+ while (cell != null && !this.isValidDropTarget(cell, cells, evt) &&
+ !this.model.isLayer(cell))
+ {
+ cell = this.model.getParent(cell);
+ }
+
+ return (!this.model.isLayer(cell) && mxUtils.indexOf(cells, cell) < 0) ? cell : null;
+};
+
+/**
+ * Group: Cell retrieval
+ */
+
+/**
+ * Function: getDefaultParent
+ *
+ * Returns <defaultParent> or <mxGraphView.currentRoot> or the first child
+ * child of <mxGraphModel.root> if both are null. The value returned by
+ * this function should be used as the parent for new cells (aka default
+ * layer).
+ */
+mxGraph.prototype.getDefaultParent = function()
+{
+ var parent = this.defaultParent;
+
+ if (parent == null)
+ {
+ parent = this.getCurrentRoot();
+
+ if (parent == null)
+ {
+ var root = this.model.getRoot();
+ parent = this.model.getChildAt(root, 0);
+ }
+ }
+
+ return parent;
+};
+
+/**
+ * Function: setDefaultParent
+ *
+ * Sets the <defaultParent> to the given cell. Set this to null to return
+ * the first child of the root in getDefaultParent.
+ */
+mxGraph.prototype.setDefaultParent = function(cell)
+{
+ this.defaultParent = cell;
+};
+
+/**
+ * Function: getSwimlane
+ *
+ * Returns the nearest ancestor of the given cell which is a swimlane, or
+ * the given cell, if it is itself a swimlane.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the ancestor swimlane should be returned.
+ */
+mxGraph.prototype.getSwimlane = function(cell)
+{
+ while (cell != null && !this.isSwimlane(cell))
+ {
+ cell = this.model.getParent(cell);
+ }
+
+ return cell;
+};
+
+/**
+ * Function: getSwimlaneAt
+ *
+ * Returns the bottom-most swimlane that intersects the given point (x, y)
+ * in the cell hierarchy that starts at the given parent.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.getSwimlaneAt = function (x, y, parent)
+{
+ parent = parent || this.getDefaultParent();
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(parent, i);
+ var result = this.getSwimlaneAt(x, y, child);
+
+ if (result != null)
+ {
+ return result;
+ }
+ else if (this.isSwimlane(child))
+ {
+ var state = this.view.getState(child);
+
+ if (this.intersects(state, x, y))
+ {
+ return child;
+ }
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: getCellAt
+ *
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy starting at the given parent. This will also return
+ * swimlanes if the given location intersects the content area of the
+ * swimlane. If this is not desired, then the <hitsSwimlaneContent> may be
+ * used if the returned cell is a swimlane to determine if the location
+ * is inside the content area or on the actual title of the swimlane.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is <defaultParent>.
+ * vertices - Optional boolean indicating if vertices should be returned.
+ * Default is true.
+ * edges - Optional boolean indicating if edges should be returned. Default
+ * is true.
+ */
+mxGraph.prototype.getCellAt = function(x, y, parent, vertices, edges)
+{
+ vertices = (vertices != null) ? vertices : true;
+ edges = (edges != null) ? edges : true;
+ parent = (parent != null) ? parent : this.getDefaultParent();
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = childCount - 1; i >= 0; i--)
+ {
+ var cell = this.model.getChildAt(parent, i);
+ var result = this.getCellAt(x, y, cell, vertices, edges);
+
+ if (result != null)
+ {
+ return result;
+ }
+ else if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) ||
+ vertices && this.model.isVertex(cell)))
+ {
+ var state = this.view.getState(cell);
+
+ if (this.intersects(state, x, y))
+ {
+ return cell;
+ }
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: intersects
+ *
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy that starts at the given parent.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the cell state.
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ */
+mxGraph.prototype.intersects = function(state, x, y)
+{
+ if (state != null)
+ {
+ var pts = state.absolutePoints;
+
+ if (pts != null)
+ {
+ var t2 = this.tolerance * this.tolerance;
+
+ var pt = pts[0];
+
+ for (var i = 1; i<pts.length; i++)
+ {
+ var next = pts[i];
+ var dist = mxUtils.ptSegDistSq(
+ pt.x, pt.y, next.x, next.y, x, y);
+
+ if (dist <= t2)
+ {
+ return true;
+ }
+
+ pt = next;
+ }
+ }
+ else if (mxUtils.contains(state, x, y))
+ {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Function: hitsSwimlaneContent
+ *
+ * Returns true if the given coordinate pair is inside the content
+ * are of the given swimlane.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> that specifies the swimlane.
+ * x - X-coordinate of the mouse event.
+ * y - Y-coordinate of the mouse event.
+ */
+mxGraph.prototype.hitsSwimlaneContent = function(swimlane, x, y)
+{
+ var state = this.getView().getState(swimlane);
+ var size = this.getStartSize(swimlane);
+
+ if (state != null)
+ {
+ var scale = this.getView().getScale();
+ x -= state.x;
+ y -= state.y;
+
+ if (size.width > 0 && x > 0 && x > size.width * scale)
+ {
+ return true;
+ }
+ else if (size.height > 0 && y > 0 && y > size.height * scale)
+ {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Function: getChildVertices
+ *
+ * Returns the visible child vertices of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be returned.
+ */
+mxGraph.prototype.getChildVertices = function(parent)
+{
+ return this.getChildCells(parent, true, false);
+};
+
+/**
+ * Function: getChildEdges
+ *
+ * Returns the visible child edges of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose child vertices should be returned.
+ */
+mxGraph.prototype.getChildEdges = function(parent)
+{
+ return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ *
+ * Returns the visible child vertices or edges in the given parent. If
+ * vertices and edges is false, then all children are returned.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be returned.
+ * vertices - Optional boolean that specifies if child vertices should
+ * be returned. Default is false.
+ * edges - Optional boolean that specifies if child edges should
+ * be returned. Default is false.
+ */
+mxGraph.prototype.getChildCells = function(parent, vertices, edges)
+{
+ parent = (parent != null) ? parent : this.getDefaultParent();
+ vertices = (vertices != null) ? vertices : false;
+ edges = (edges != null) ? edges : false;
+
+ var cells = this.model.getChildCells(parent, vertices, edges);
+ var result = [];
+
+ // Filters out the non-visible child cells
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.isCellVisible(cells[i]))
+ {
+ result.push(cells[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getConnections
+ *
+ * Returns all visible edges connected to the given cell without loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose connections should be returned.
+ * parent - Optional parent of the opposite end for a connection to be
+ * returned.
+ */
+mxGraph.prototype.getConnections = function(cell, parent)
+{
+ return this.getEdges(cell, parent, true, true, false);
+};
+
+/**
+ * Function: getIncomingEdges
+ *
+ * Returns the visible incoming edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose incoming edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getIncomingEdges = function(cell, parent)
+{
+ return this.getEdges(cell, parent, true, false, false);
+};
+
+/**
+ * Function: getOutgoingEdges
+ *
+ * Returns the visible outgoing edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose outgoing edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getOutgoingEdges = function(cell, parent)
+{
+ return this.getEdges(cell, parent, false, true, false);
+};
+
+/**
+ * Function: getEdges
+ *
+ * Returns the incoming and/or outgoing edges for the given cell.
+ * If the optional parent argument is specified, then only edges are returned
+ * where the opposite is in the given parent cell. If at least one of incoming
+ * or outgoing is true, then loops are ignored, if both are false, then all
+ * edges connected to the given cell are returned including loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ * incoming - Optional boolean that specifies if incoming edges should
+ * be included in the result. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should
+ * be included in the result. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be
+ * included in the result. Default is true.
+ * recurse - Optional boolean the specifies if the parent specified only
+ * need be an ancestral parent, true, or the direct parent, false.
+ * Default is false
+ */
+mxGraph.prototype.getEdges = function(cell, parent, incoming, outgoing, includeLoops, recurse)
+{
+ incoming = (incoming != null) ? incoming : true;
+ outgoing = (outgoing != null) ? outgoing : true;
+ includeLoops = (includeLoops != null) ? includeLoops : true;
+ recurse = (recurse != null) ? recurse : false;
+
+ var edges = [];
+ var isCollapsed = this.isCellCollapsed(cell);
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(cell, i);
+
+ if (isCollapsed || !this.isCellVisible(child))
+ {
+ edges = edges.concat(this.model.getEdges(child, incoming, outgoing));
+ }
+ }
+
+ edges = edges.concat(this.model.getEdges(cell, incoming, outgoing));
+ var result = [];
+
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.view.getState(edges[i]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+ if ((includeLoops && source == target) || ((source != target) && ((incoming &&
+ target == cell && (parent == null || this.isValidAncestor(source, parent, recurse))) ||
+ (outgoing && source == cell && (parent == null ||
+ this.isValidAncestor(target, parent, recurse))))))
+ {
+ result.push(edges[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: isValidAncestor
+ *
+ * Returns whether or not the specified parent is a valid
+ * ancestor of the specified cell, either direct or indirectly
+ * based on whether ancestor recursion is enabled.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> the possible child cell
+ * parent - <mxCell> the possible parent cell
+ * recurse - boolean whether or not to recurse the child ancestors
+ */
+mxGraph.prototype.isValidAncestor = function(cell, parent, recurse)
+{
+ return (recurse ? this.model.isAncestor(parent, cell) : this.model
+ .getParent(cell) == parent);
+};
+
+/**
+ * Function: getOpposites
+ *
+ * Returns all distinct visible opposite cells for the specified terminal
+ * on the given edges.
+ *
+ * Parameters:
+ *
+ * edges - Array of <mxCells> that contains the edges whose opposite
+ * terminals should be returned.
+ * terminal - Terminal that specifies the end whose opposite should be
+ * returned.
+ * source - Optional boolean that specifies if source terminals should be
+ * included in the result. Default is true.
+ * targets - Optional boolean that specifies if targer terminals should be
+ * included in the result. Default is true.
+ */
+mxGraph.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+ sources = (sources != null) ? sources : true;
+ targets = (targets != null) ? targets : true;
+
+ var terminals = [];
+
+ // Implements set semantic on the terminals array using a string
+ // representation of each cell in an associative array lookup
+ var hash = new Object();
+
+ if (edges != null)
+ {
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.view.getState(edges[i]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+ // Checks if the terminal is the source of the edge and if the
+ // target should be stored in the result
+ if (source == terminal && target != null &&
+ target != terminal && targets)
+ {
+ var id = mxCellPath.create(target);
+
+ if (hash[id] == null)
+ {
+ hash[id] = target;
+ terminals.push(target);
+ }
+ }
+
+ // Checks if the terminal is the taget of the edge and if the
+ // source should be stored in the result
+ else if (target == terminal && source != null &&
+ source != terminal && sources)
+ {
+ var id = mxCellPath.create(source);
+
+ if (hash[id] == null)
+ {
+ hash[id] = source;
+ terminals.push(source);
+ }
+ }
+ }
+ }
+
+ return terminals;
+};
+
+/**
+ * Function: getEdgesBetween
+ *
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and returns the connected edges
+ * as displayed on the screen.
+ *
+ * Parameters:
+ *
+ * source -
+ * target -
+ * directed -
+ */
+mxGraph.prototype.getEdgesBetween = function(source, target, directed)
+{
+ directed = (directed != null) ? directed : false;
+ var edges = this.getEdges(source);
+ var result = [];
+
+ // Checks if the edge is connected to the correct
+ // cell and returns the first match
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.view.getState(edges[i]);
+
+ var src = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+ var trg = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+ if ((src == source && trg == target) || (!directed && src == target && trg == source))
+ {
+ result.push(edges[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getPointForEvent
+ *
+ * Returns an <mxPoint> representing the given event in the unscaled,
+ * non-translated coordinate space of <container> and applies the grid.
+ *
+ * Parameters:
+ *
+ * evt - Mousevent that contains the mouse pointer location.
+ * addOffset - Optional boolean that specifies if the position should be
+ * offset by half of the <gridSize>. Default is true.
+ */
+ mxGraph.prototype.getPointForEvent = function(evt, addOffset)
+ {
+ var p = mxUtils.convertPoint(this.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ var s = this.view.scale;
+ var tr = this.view.translate;
+ var off = (addOffset != false) ? this.gridSize / 2 : 0;
+
+ p.x = this.snap(p.x / s - tr.x - off);
+ p.y = this.snap(p.y / s - tr.y - off);
+
+ return p;
+ };
+
+/**
+ * Function: getCells
+ *
+ * Returns the children of the given parent that are contained in the given
+ * rectangle (x, y, width, height). The result is added to the optional
+ * result array, which is returned from the function. If no result array
+ * is specified then a new array is created and returned.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the rectangle.
+ * y - Y-coordinate of the rectangle.
+ * width - Width of the rectangle.
+ * height - Height of the rectangle.
+ * parent - <mxCell> whose children should be checked. Default is
+ * <defaultParent>.
+ * result - Optional array to store the result in.
+ */
+mxGraph.prototype.getCells = function(x, y, width, height, parent, result)
+{
+ result = (result != null) ? result : [];
+
+ if (width > 0 || height > 0)
+ {
+ var right = x + width;
+ var bottom = y + height;
+
+ parent = parent || this.getDefaultParent();
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cell = this.model.getChildAt(parent, i);
+ var state = this.view.getState(cell);
+
+ if (this.isCellVisible(cell) && state != null)
+ {
+ if (state.x >= x && state.y >= y &&
+ state.x + state.width <= right &&
+ state.y + state.height <= bottom)
+ {
+ result.push(cell);
+ }
+ else
+ {
+ this.getCells(x, y, width, height, cell, result);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getCellsBeyond
+ *
+ * Returns the children of the given parent that are contained in the
+ * halfpane from the given point (x0, y0) rightwards and/or downwards
+ * depending on rightHalfpane and bottomHalfpane.
+ *
+ * Parameters:
+ *
+ * x0 - X-coordinate of the origin.
+ * y0 - Y-coordinate of the origin.
+ * parent - Optional <mxCell> whose children should be checked. Default is
+ * <defaultParent>.
+ * rightHalfpane - Boolean indicating if the cells in the right halfpane
+ * from the origin should be returned.
+ * bottomHalfpane - Boolean indicating if the cells in the bottom halfpane
+ * from the origin should be returned.
+ */
+mxGraph.prototype.getCellsBeyond = function(x0, y0, parent, rightHalfpane, bottomHalfpane)
+{
+ var result = [];
+
+ if (rightHalfpane || bottomHalfpane)
+ {
+ if (parent == null)
+ {
+ parent = this.getDefaultParent();
+ }
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(parent, i);
+ var state = this.view.getState(child);
+
+ if (this.isCellVisible(child) && state != null)
+ {
+ if ((!rightHalfpane ||
+ state.x >= x0) &&
+ (!bottomHalfpane ||
+ state.y >= y0))
+ {
+ result.push(child);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: findTreeRoots
+ *
+ * Returns all children in the given parent which do not have incoming
+ * edges. If the result is empty then the with the greatest difference
+ * between incoming and outgoing edges is returned.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be checked.
+ * isolate - Optional boolean that specifies if edges should be ignored if
+ * the opposite end is not a child of the given parent cell. Default is
+ * false.
+ * invert - Optional boolean that specifies if outgoing or incoming edges
+ * should be counted for a tree root. If false then outgoing edges will be
+ * counted. Default is false.
+ */
+mxGraph.prototype.findTreeRoots = function(parent, isolate, invert)
+{
+ isolate = (isolate != null) ? isolate : false;
+ invert = (invert != null) ? invert : false;
+ var roots = [];
+
+ if (parent != null)
+ {
+ var model = this.getModel();
+ var childCount = model.getChildCount(parent);
+ var best = null;
+ var maxDiff = 0;
+
+ for (var i=0; i<childCount; i++)
+ {
+ var cell = model.getChildAt(parent, i);
+
+ if (this.model.isVertex(cell) && this.isCellVisible(cell))
+ {
+ var conns = this.getConnections(cell, (isolate) ? parent : null);
+ var fanOut = 0;
+ var fanIn = 0;
+
+ for (var j = 0; j < conns.length; j++)
+ {
+ var src = this.view.getVisibleTerminal(conns[j], true);
+
+ if (src == cell)
+ {
+ fanOut++;
+ }
+ else
+ {
+ fanIn++;
+ }
+ }
+
+ if ((invert && fanOut == 0 && fanIn > 0) ||
+ (!invert && fanIn == 0 && fanOut > 0))
+ {
+ roots.push(cell);
+ }
+
+ var diff = (invert) ? fanIn - fanOut : fanOut - fanIn;
+
+ if (diff > maxDiff)
+ {
+ maxDiff = diff;
+ best = cell;
+ }
+ }
+ }
+
+ if (roots.length == 0 && best != null)
+ {
+ roots.push(best);
+ }
+ }
+
+ return roots;
+};
+
+/**
+ * Function: traverse
+ *
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ * mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional array of cell paths for the visited cells.
+ */
+mxGraph.prototype.traverse = function(vertex, directed, func, edge, visited)
+{
+ if (func != null && vertex != null)
+ {
+ directed = (directed != null) ? directed : true;
+ visited = visited || [];
+ var id = mxCellPath.create(vertex);
+
+ if (visited[id] == null)
+ {
+ visited[id] = vertex;
+ var result = func(vertex, edge);
+
+ if (result == null || result)
+ {
+ var edgeCount = this.model.getEdgeCount(vertex);
+
+ if (edgeCount > 0)
+ {
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var e = this.model.getEdgeAt(vertex, i);
+ var isSource = this.model.getTerminal(e, true) == vertex;
+
+ if (!directed || isSource)
+ {
+ var next = this.model.getTerminal(e, !isSource);
+ this.traverse(next, directed, func, e, visited);
+ }
+ }
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Group: Selection
+ */
+
+/**
+ * Function: isCellSelected
+ *
+ * Returns true if the given cell is selected.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the selection state should be returned.
+ */
+mxGraph.prototype.isCellSelected = function(cell)
+{
+ return this.getSelectionModel().isSelected(cell);
+};
+
+/**
+ * Function: isSelectionEmpty
+ *
+ * Returns true if the selection is empty.
+ */
+mxGraph.prototype.isSelectionEmpty = function()
+{
+ return this.getSelectionModel().isEmpty();
+};
+
+/**
+ * Function: clearSelection
+ *
+ * Clears the selection using <mxGraphSelectionModel.clear>.
+ */
+mxGraph.prototype.clearSelection = function()
+{
+ return this.getSelectionModel().clear();
+};
+
+/**
+ * Function: getSelectionCount
+ *
+ * Returns the number of selected cells.
+ */
+mxGraph.prototype.getSelectionCount = function()
+{
+ return this.getSelectionModel().cells.length;
+};
+
+/**
+ * Function: getSelectionCell
+ *
+ * Returns the first cell from the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCell = function()
+{
+ return this.getSelectionModel().cells[0];
+};
+
+/**
+ * Function: getSelectionCells
+ *
+ * Returns the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCells = function()
+{
+ return this.getSelectionModel().cells.slice();
+};
+
+/**
+ * Function: setSelectionCell
+ *
+ * Sets the selection cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be selected.
+ */
+mxGraph.prototype.setSelectionCell = function(cell)
+{
+ this.getSelectionModel().setCell(cell);
+};
+
+/**
+ * Function: setSelectionCells
+ *
+ * Sets the selection cell.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraph.prototype.setSelectionCells = function(cells)
+{
+ this.getSelectionModel().setCells(cells);
+};
+
+/**
+ * Function: addSelectionCell
+ *
+ * Adds the given cell to the selection.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be add to the selection.
+ */
+mxGraph.prototype.addSelectionCell = function(cell)
+{
+ this.getSelectionModel().addCell(cell);
+};
+
+/**
+ * Function: addSelectionCells
+ *
+ * Adds the given cells to the selection.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be added to the selection.
+ */
+mxGraph.prototype.addSelectionCells = function(cells)
+{
+ this.getSelectionModel().addCells(cells);
+};
+
+/**
+ * Function: removeSelectionCell
+ *
+ * Removes the given cell from the selection.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCell = function(cell)
+{
+ this.getSelectionModel().removeCell(cell);
+};
+
+/**
+ * Function: removeSelectionCells
+ *
+ * Removes the given cells from the selection.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCells = function(cells)
+{
+ this.getSelectionModel().removeCells(cells);
+};
+
+/**
+ * Function: selectRegion
+ *
+ * Selects and returns the cells inside the given rectangle for the
+ * specified event.
+ *
+ * Parameters:
+ *
+ * rect - <mxRectangle> that represents the region to be selected.
+ * evt - Mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectRegion = function(rect, evt)
+{
+ var cells = this.getCells(rect.x, rect.y, rect.width, rect.height);
+ this.selectCellsForEvent(cells, evt);
+
+ return cells;
+};
+
+/**
+ * Function: selectNextCell
+ *
+ * Selects the next cell.
+ */
+mxGraph.prototype.selectNextCell = function()
+{
+ this.selectCell(true);
+};
+
+/**
+ * Function: selectPreviousCell
+ *
+ * Selects the previous cell.
+ */
+mxGraph.prototype.selectPreviousCell = function()
+{
+ this.selectCell();
+};
+
+/**
+ * Function: selectParentCell
+ *
+ * Selects the parent cell.
+ */
+mxGraph.prototype.selectParentCell = function()
+{
+ this.selectCell(false, true);
+};
+
+/**
+ * Function: selectChildCell
+ *
+ * Selects the first child cell.
+ */
+mxGraph.prototype.selectChildCell = function()
+{
+ this.selectCell(false, false, true);
+};
+
+/**
+ * Function: selectCell
+ *
+ * Selects the next, parent, first child or previous cell, if all arguments
+ * are false.
+ *
+ * Parameters:
+ *
+ * isNext - Boolean indicating if the next cell should be selected.
+ * isParent - Boolean indicating if the parent cell should be selected.
+ * isChild - Boolean indicating if the first child cell should be selected.
+ */
+mxGraph.prototype.selectCell = function(isNext, isParent, isChild)
+{
+ var sel = this.selectionModel;
+ var cell = (sel.cells.length > 0) ? sel.cells[0] : null;
+
+ if (sel.cells.length > 1)
+ {
+ sel.clear();
+ }
+
+ var parent = (cell != null) ?
+ this.model.getParent(cell) :
+ this.getDefaultParent();
+
+ var childCount = this.model.getChildCount(parent);
+
+ if (cell == null && childCount > 0)
+ {
+ var child = this.model.getChildAt(parent, 0);
+ this.setSelectionCell(child);
+ }
+ else if ((cell == null || isParent) &&
+ this.view.getState(parent) != null &&
+ this.model.getGeometry(parent) != null)
+ {
+ if (this.getCurrentRoot() != parent)
+ {
+ this.setSelectionCell(parent);
+ }
+ }
+ else if (cell != null && isChild)
+ {
+ var tmp = this.model.getChildCount(cell);
+
+ if (tmp > 0)
+ {
+ var child = this.model.getChildAt(cell, 0);
+ this.setSelectionCell(child);
+ }
+ }
+ else if (childCount > 0)
+ {
+ var i = parent.getIndex(cell);
+
+ if (isNext)
+ {
+ i++;
+ var child = this.model.getChildAt(parent, i % childCount);
+ this.setSelectionCell(child);
+ }
+ else
+ {
+ i--;
+ var index = (i < 0) ? childCount - 1 : i;
+ var child = this.model.getChildAt(parent, index);
+ this.setSelectionCell(child);
+ }
+ }
+};
+
+/**
+ * Function: selectAll
+ *
+ * Selects all children of the given parent cell or the children of the
+ * default parent if no parent is specified. To select leaf vertices and/or
+ * edges use <selectCells>.
+ *
+ * Parameters:
+ *
+ * parent - Optional <mxCell> whose children should be selected.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.selectAll = function(parent)
+{
+ parent = parent || this.getDefaultParent();
+
+ var children = this.model.getChildren(parent);
+
+ if (children != null)
+ {
+ this.setSelectionCells(children);
+ }
+};
+
+/**
+ * Function: selectVertices
+ *
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectVertices = function(parent)
+{
+ this.selectCells(true, false, parent);
+};
+
+/**
+ * Function: selectVertices
+ *
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectEdges = function(parent)
+{
+ this.selectCells(false, true, parent);
+};
+
+/**
+ * Function: selectCells
+ *
+ * Selects all vertices and/or edges depending on the given boolean
+ * arguments recursively, starting at the given parent or the default
+ * parent if no parent is specified. Use <selectAll> to select all cells.
+ *
+ * Parameters:
+ *
+ * vertices - Boolean indicating if vertices should be selected.
+ * edges - Boolean indicating if edges should be selected.
+ * parent - Optional <mxCell> that acts as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.selectCells = function(vertices, edges, parent)
+{
+ parent = parent || this.getDefaultParent();
+
+ var filter = mxUtils.bind(this, function(cell)
+ {
+ return this.view.getState(cell) != null &&
+ this.model.getChildCount(cell) == 0 &&
+ ((this.model.isVertex(cell) && vertices) ||
+ (this.model.isEdge(cell) && edges));
+ });
+
+ var cells = this.model.filterDescendants(filter, parent);
+ this.setSelectionCells(cells);
+};
+
+/**
+ * Function: selectCellForEvent
+ *
+ * Selects the given cell by either adding it to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellForEvent = function(cell, evt)
+{
+ var isSelected = this.isCellSelected(cell);
+
+ if (this.isToggleEvent(evt))
+ {
+ if (isSelected)
+ {
+ this.removeSelectionCell(cell);
+ }
+ else
+ {
+ this.addSelectionCell(cell);
+ }
+ }
+ else if (!isSelected || this.getSelectionCount() != 1)
+ {
+ this.setSelectionCell(cell);
+ }
+};
+
+/**
+ * Function: selectCellsForEvent
+ *
+ * Selects the given cells by either adding them to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellsForEvent = function(cells, evt)
+{
+ if (this.isToggleEvent(evt))
+ {
+ this.addSelectionCells(cells);
+ }
+ else
+ {
+ this.setSelectionCells(cells);
+ }
+};
+
+/**
+ * Group: Selection state
+ */
+
+/**
+ * Function: createHandler
+ *
+ * Creates a new handler for the given cell state. This implementation
+ * returns a new <mxEdgeHandler> of the corresponding cell is an edge,
+ * otherwise it returns an <mxVertexHandler>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose handler should be created.
+ */
+mxGraph.prototype.createHandler = function(state)
+{
+ var result = null;
+
+ if (state != null)
+ {
+ if (this.model.isEdge(state.cell))
+ {
+ var style = this.view.getEdgeStyle(state);
+
+ if (this.isLoop(state) ||
+ style == mxEdgeStyle.ElbowConnector ||
+ style == mxEdgeStyle.SideToSide ||
+ style == mxEdgeStyle.TopToBottom)
+ {
+ result = new mxElbowEdgeHandler(state);
+ }
+ else if (style == mxEdgeStyle.SegmentConnector ||
+ style == mxEdgeStyle.OrthConnector)
+ {
+ result = new mxEdgeSegmentHandler(state);
+ }
+ else
+ {
+ result = new mxEdgeHandler(state);
+ }
+ }
+ else
+ {
+ result = new mxVertexHandler(state);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Group: Graph events
+ */
+
+/**
+ * Function: addMouseListener
+ *
+ * Adds a listener to the graph event dispatch loop. The listener
+ * must implement the mouseDown, mouseMove and mouseUp methods
+ * as shown in the <mxMouseEvent> class.
+ *
+ * Parameters:
+ *
+ * listener - Listener to be added to the graph event listeners.
+ */
+mxGraph.prototype.addMouseListener = function(listener)
+{
+ if (this.mouseListeners == null)
+ {
+ this.mouseListeners = [];
+ }
+
+ this.mouseListeners.push(listener);
+};
+
+/**
+ * Function: removeMouseListener
+ *
+ * Removes the specified graph listener.
+ *
+ * Parameters:
+ *
+ * listener - Listener to be removed from the graph event listeners.
+ */
+mxGraph.prototype.removeMouseListener = function(listener)
+{
+ if (this.mouseListeners != null)
+ {
+ for (var i = 0; i < this.mouseListeners.length; i++)
+ {
+ if (this.mouseListeners[i] == listener)
+ {
+ this.mouseListeners.splice(i, 1);
+ break;
+ }
+ }
+ }
+};
+
+/**
+ * Function: updateMouseEvent
+ *
+ * Sets the graphX and graphY properties if the given <mxMouseEvent> if
+ * required.
+ */
+mxGraph.prototype.updateMouseEvent = function(me)
+{
+ if (me.graphX == null || me.graphY == null)
+ {
+ var pt = mxUtils.convertPoint(this.container, me.getX(), me.getY());
+
+ me.graphX = pt.x - this.panDx;
+ me.graphY = pt.y - this.panDy;
+ }
+};
+
+/**
+ * Function: fireMouseEvent
+ *
+ * Dispatches the given event in the graph event dispatch loop. Possible
+ * event names are <mxEvent.MOUSE_DOWN>, <mxEvent.MOUSE_MOVE> and
+ * <mxEvent.MOUSE_UP>. All listeners are invoked for all events regardless
+ * of the consumed state of the event.
+ *
+ * Parameters:
+ *
+ * evtName - String that specifies the type of event to be dispatched.
+ * me - <mxMouseEvent> to be fired.
+ * sender - Optional sender argument. Default is this.
+ */
+mxGraph.prototype.fireMouseEvent = function(evtName, me, sender)
+{
+ if (sender == null)
+ {
+ sender = this;
+ }
+
+ // Updates the graph coordinates in the event
+ this.updateMouseEvent(me);
+
+ // Makes sure we have a uniform event-sequence across all
+ // browsers for a double click. Since evt.detail == 2 is only
+ // available on Firefox we use the fact that each mousedown
+ // must be followed by a mouseup, all out-of-sync downs
+ // will be dropped silently.
+ if (evtName == mxEvent.MOUSE_DOWN)
+ {
+ this.isMouseDown = true;
+ }
+
+ // Detects and processes double taps for touch-based devices
+ // which do not have native double click events
+ if (mxClient.IS_TOUCH && this.doubleTapEnabled && evtName == mxEvent.MOUSE_DOWN)
+ {
+ var currentTime = new Date().getTime();
+
+ if (currentTime - this.lastTouchTime < this.doubleTapTimeout &&
+ Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
+ Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance)
+ {
+ // FIXME: The actual editing should start on MOUSE_UP event but
+ // the detection of the double click should use the mouse_down event
+ // to make it consistent with behaviour in browser with mouse.
+ this.lastTouchTime = 0;
+ this.dblClick(me.getEvent(), me.getCell());
+
+ // Stop bubbling but do not consume to make sure the device
+ // can bring up the virtual keyboard for editing
+ me.getEvent().cancelBubble = true;
+ }
+ else
+ {
+ this.lastTouchX = me.getX();
+ this.lastTouchY = me.getY();
+ this.lastTouchTime = currentTime;
+ }
+ }
+
+ // Workaround for IE9 standards mode ignoring tolerance for double clicks
+ var noDoubleClick = me.getEvent().detail/*clickCount*/ != 2;
+
+ if (mxClient.IS_IE && document.compatMode == 'CSS1Compat')
+ {
+ if ((this.lastMouseX != null && Math.abs(this.lastMouseX - me.getX()) > this.doubleTapTolerance) ||
+ (this.lastMouseY != null && Math.abs(this.lastMouseY - me.getY()) > this.doubleTapTolerance))
+ {
+ noDoubleClick = true;
+ }
+
+ if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.lastMouseX = me.getX();
+ this.lastMouseY = me.getY();
+ }
+ }
+
+ // Filters too many mouse ups when the mouse is down
+ if ((evtName != mxEvent.MOUSE_UP || this.isMouseDown) && noDoubleClick)
+ {
+ if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.isMouseDown = false;
+ }
+
+ if (!this.isEditing() && (mxClient.IS_OP || mxClient.IS_SF || mxClient.IS_GC ||
+ (mxClient.IS_IE && mxClient.IS_SVG) || me.getEvent().target != this.container))
+ {
+ if (evtName == mxEvent.MOUSE_MOVE && this.isMouseDown && this.autoScroll)
+ {
+ this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend);
+ }
+
+ if (this.mouseListeners != null)
+ {
+ var args = [sender, me];
+
+ // Does not change returnValue in Opera
+ me.getEvent().returnValue = true;
+
+ for (var i = 0; i < this.mouseListeners.length; i++)
+ {
+ var l = this.mouseListeners[i];
+
+ if (evtName == mxEvent.MOUSE_DOWN)
+ {
+ l.mouseDown.apply(l, args);
+ }
+ else if (evtName == mxEvent.MOUSE_MOVE)
+ {
+ l.mouseMove.apply(l, args);
+ }
+ else if (evtName == mxEvent.MOUSE_UP)
+ {
+ l.mouseUp.apply(l, args);
+ }
+ }
+ }
+
+ // Invokes the click handler
+ if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.click(me);
+ }
+ }
+ }
+ else if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.isMouseDown = false;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the graph and all its resources.
+ */
+mxGraph.prototype.destroy = function()
+{
+ if (!this.destroyed)
+ {
+ this.destroyed = true;
+
+ if (this.tooltipHandler != null)
+ {
+ this.tooltipHandler.destroy();
+ }
+
+ if (this.selectionCellsHandler != null)
+ {
+ this.selectionCellsHandler.destroy();
+ }
+
+ if (this.panningHandler != null)
+ {
+ this.panningHandler.destroy();
+ }
+
+ if (this.connectionHandler != null)
+ {
+ this.connectionHandler.destroy();
+ }
+
+ if (this.graphHandler != null)
+ {
+ this.graphHandler.destroy();
+ }
+
+ if (this.cellEditor != null)
+ {
+ this.cellEditor.destroy();
+ }
+
+ if (this.view != null)
+ {
+ this.view.destroy();
+ }
+
+ if (this.model != null && this.graphModelChangeListener != null)
+ {
+ this.model.removeListener(this.graphModelChangeListener);
+ this.graphModelChangeListener = null;
+ }
+
+ this.container = null;
+ }
+};
diff --git a/src/js/view/mxGraphSelectionModel.js b/src/js/view/mxGraphSelectionModel.js
new file mode 100644
index 0000000..5cd16a8
--- /dev/null
+++ b/src/js/view/mxGraphSelectionModel.js
@@ -0,0 +1,435 @@
+/**
+ * $Id: mxGraphSelectionModel.js,v 1.14 2011-11-25 10:16:08 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphSelectionModel
+ *
+ * Implements the selection model for a graph. Here is a listener that handles
+ * all removed selection cells.
+ *
+ * (code)
+ * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ * var cells = evt.getProperty('added');
+ *
+ * for (var i = 0; i < cells.length; i++)
+ * {
+ * // Handle cells[i]...
+ * }
+ * });
+ * (end)
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires after the selection was changed in <changeSelection>. The
+ * <code>edit</code> property contains the <mxUndoableEdit> which contains the
+ * <mxSelectionChange>.
+ *
+ * Event: mxEvent.CHANGE
+ *
+ * Fires after the selection changes by executing an <mxSelectionChange>. The
+ * <code>added</code> and <code>removed</code> properties contain arrays of
+ * cells that have been added to or removed from the selection, respectively.
+ *
+ * Constructor: mxGraphSelectionModel
+ *
+ * Constructs a new graph selection model for the given <mxGraph>.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphSelectionModel(graph)
+{
+ this.graph = graph;
+ this.cells = [];
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphSelectionModel.prototype = new mxEventSource();
+mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel;
+
+/**
+ * Variable: doneResource
+ *
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphSelectionModel.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Variable: updatingSelectionResource
+ *
+ * Specifies the resource key for the status message while the selection is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingSelection'.
+ */
+mxGraphSelectionModel.prototype.updatingSelectionResource = (mxClient.language != 'none') ? 'updatingSelection' : '';
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphSelectionModel.prototype.graph = null;
+
+/**
+ * Variable: singleSelection
+ *
+ * Specifies if only one selected item at a time is allowed.
+ * Default is false.
+ */
+mxGraphSelectionModel.prototype.singleSelection = false;
+
+/**
+ * Function: isSingleSelection
+ *
+ * Returns <singleSelection> as a boolean.
+ */
+mxGraphSelectionModel.prototype.isSingleSelection = function()
+{
+ return this.singleSelection;
+};
+
+/**
+ * Function: setSingleSelection
+ *
+ * Sets the <singleSelection> flag.
+ *
+ * Parameters:
+ *
+ * singleSelection - Boolean that specifies the new value for
+ * <singleSelection>.
+ */
+mxGraphSelectionModel.prototype.setSingleSelection = function(singleSelection)
+{
+ this.singleSelection = singleSelection;
+};
+
+/**
+ * Function: isSelected
+ *
+ * Returns true if the given <mxCell> is selected.
+ */
+mxGraphSelectionModel.prototype.isSelected = function(cell)
+{
+ if (cell != null)
+ {
+ return mxUtils.indexOf(this.cells, cell) >= 0;
+ }
+
+ return false;
+};
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if no cells are currently selected.
+ */
+mxGraphSelectionModel.prototype.isEmpty = function()
+{
+ return this.cells.length == 0;
+};
+
+/**
+ * Function: clear
+ *
+ * Clears the selection and fires a <change> event if the selection was not
+ * empty.
+ */
+mxGraphSelectionModel.prototype.clear = function()
+{
+ this.changeSelection(null, this.cells);
+};
+
+/**
+ * Function: setCell
+ *
+ * Selects the specified <mxCell> using <setCells>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCell = function(cell)
+{
+ if (cell != null)
+ {
+ this.setCells([cell]);
+ }
+};
+
+/**
+ * Function: setCells
+ *
+ * Selects the given array of <mxCells> and fires a <change> event.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCells = function(cells)
+{
+ if (cells != null)
+ {
+ if (this.singleSelection)
+ {
+ cells = [this.getFirstSelectableCell(cells)];
+ }
+
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.graph.isCellSelectable(cells[i]))
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ this.changeSelection(tmp, this.cells);
+ }
+};
+
+/**
+ * Function: getFirstSelectableCell
+ *
+ * Returns the first selectable cell in the given array of cells.
+ */
+mxGraphSelectionModel.prototype.getFirstSelectableCell = function(cells)
+{
+ if (cells != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.graph.isCellSelectable(cells[i]))
+ {
+ return cells[i];
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: addCell
+ *
+ * Adds the given <mxCell> to the selection and fires a <select> event.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCell = function(cell)
+{
+ if (cell != null)
+ {
+ this.addCells([cell]);
+ }
+};
+
+/**
+ * Function: addCells
+ *
+ * Adds the given array of <mxCells> to the selection and fires a <select>
+ * event.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCells = function(cells)
+{
+ if (cells != null)
+ {
+ var remove = null;
+
+ if (this.singleSelection)
+ {
+ remove = this.cells;
+ cells = [this.getFirstSelectableCell(cells)];
+ }
+
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isSelected(cells[i]) &&
+ this.graph.isCellSelectable(cells[i]))
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ this.changeSelection(tmp, remove);
+ }
+};
+
+/**
+ * Function: removeCell
+ *
+ * Removes the specified <mxCell> from the selection and fires a <select>
+ * event for the remaining cells.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.removeCell = function(cell)
+{
+ if (cell != null)
+ {
+ this.removeCells([cell]);
+ }
+};
+
+/**
+ * Function: removeCells
+ */
+mxGraphSelectionModel.prototype.removeCells = function(cells)
+{
+ if (cells != null)
+ {
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.isSelected(cells[i]))
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ this.changeSelection(null, tmp);
+ }
+};
+
+/**
+ * Function: changeSelection
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ *
+ * Paramters:
+ *
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.changeSelection = function(added, removed)
+{
+ if ((added != null &&
+ added.length > 0 &&
+ added[0] != null) ||
+ (removed != null &&
+ removed.length > 0 &&
+ removed[0] != null))
+ {
+ var change = new mxSelectionChange(this, added, removed);
+ change.execute();
+ var edit = new mxUndoableEdit(this, false);
+ edit.add(change);
+ this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ }
+};
+
+/**
+ * Function: cellAdded
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ *
+ * Paramters:
+ *
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.cellAdded = function(cell)
+{
+ if (cell != null &&
+ !this.isSelected(cell))
+ {
+ this.cells.push(cell);
+ }
+};
+
+/**
+ * Function: cellRemoved
+ *
+ * Inner callback to remove the specified <mxCell> from the selection. No
+ * event is fired in this implementation.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.cellRemoved = function(cell)
+{
+ if (cell != null)
+ {
+ var index = mxUtils.indexOf(this.cells, cell);
+
+ if (index >= 0)
+ {
+ this.cells.splice(index, 1);
+ }
+ }
+};
+
+/**
+ * Class: mxSelectionChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxSelectionChange(selectionModel, added, removed)
+{
+ this.selectionModel = selectionModel;
+ this.added = (added != null) ? added.slice() : null;
+ this.removed = (removed != null) ? removed.slice() : null;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxSelectionChange.prototype.execute = function()
+{
+ var t0 = mxLog.enter('mxSelectionChange.execute');
+ window.status = mxResources.get(
+ this.selectionModel.updatingSelectionResource) ||
+ this.selectionModel.updatingSelectionResource;
+
+ if (this.removed != null)
+ {
+ for (var i = 0; i < this.removed.length; i++)
+ {
+ this.selectionModel.cellRemoved(this.removed[i]);
+ }
+ }
+
+ if (this.added != null)
+ {
+ for (var i = 0; i < this.added.length; i++)
+ {
+ this.selectionModel.cellAdded(this.added[i]);
+ }
+ }
+
+ var tmp = this.added;
+ this.added = this.removed;
+ this.removed = tmp;
+
+ window.status = mxResources.get(this.selectionModel.doneResource) ||
+ this.selectionModel.doneResource;
+ mxLog.leave('mxSelectionChange.execute', t0);
+
+ this.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ 'added', this.added, 'removed', this.removed));
+};
diff --git a/src/js/view/mxGraphView.js b/src/js/view/mxGraphView.js
new file mode 100644
index 0000000..0ef2dc8
--- /dev/null
+++ b/src/js/view/mxGraphView.js
@@ -0,0 +1,2545 @@
+/**
+ * $Id: mxGraphView.js,v 1.195 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphView
+ *
+ * Extends <mxEventSource> to implement a view for a graph. This class is in
+ * charge of computing the absolute coordinates for the relative child
+ * geometries, the points for perimeters and edge styles and keeping them
+ * cached in <mxCellStates> for faster retrieval. The states are updated
+ * whenever the model or the view state (translate, scale) changes. The scale
+ * and translate are honoured in the bounds.
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires after the root was changed in <setCurrentRoot>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> which contains the
+ * <mxCurrentRootChange>.
+ *
+ * Event: mxEvent.SCALE_AND_TRANSLATE
+ *
+ * Fires after the scale and translate have been changed in <scaleAndTranslate>.
+ * The <code>scale</code>, <code>previousScale</code>, <code>translate</code>
+ * and <code>previousTranslate</code> properties contain the new and previous
+ * scale and translate, respectively.
+ *
+ * Event: mxEvent.SCALE
+ *
+ * Fires after the scale was changed in <setScale>. The <code>scale</code> and
+ * <code>previousScale</code> properties contain the new and previous scale.
+ *
+ * Event: mxEvent.TRANSLATE
+ *
+ * Fires after the translate was changed in <setTranslate>. The
+ * <code>translate</code> and <code>previousTranslate</code> properties contain
+ * the new and previous value for translate.
+ *
+ * Event: mxEvent.DOWN and mxEvent.UP
+ *
+ * Fire if the current root is changed by executing an <mxCurrentRootChange>.
+ * The event name depends on the location of the root in the cell hierarchy
+ * with respect to the current root. The <code>root</code> and
+ * <code>previous</code> properties contain the new and previous root,
+ * respectively.
+ *
+ * Constructor: mxGraphView
+ *
+ * Constructs a new view for the given <mxGraph>.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphView(graph)
+{
+ this.graph = graph;
+ this.translate = new mxPoint();
+ this.graphBounds = new mxRectangle();
+ this.states = new mxDictionary();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphView.prototype = new mxEventSource();
+mxGraphView.prototype.constructor = mxGraphView;
+
+/**
+ *
+ */
+mxGraphView.prototype.EMPTY_POINT = new mxPoint();
+
+/**
+ * Variable: doneResource
+ *
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphView.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Function: updatingDocumentResource
+ *
+ * Specifies the resource key for the status message while the document is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingDocument'.
+ */
+mxGraphView.prototype.updatingDocumentResource = (mxClient.language != 'none') ? 'updatingDocument' : '';
+
+/**
+ * Variable: allowEval
+ *
+ * Specifies if string values in cell styles should be evaluated using
+ * <mxUtils.eval>. This will only be used if the string values can't be mapped
+ * to objects using <mxStyleRegistry>. Default is false. NOTE: Enabling this
+ * switch carries a possible security risk (see the section on security in
+ * the manual).
+ */
+mxGraphView.prototype.allowEval = false;
+
+/**
+ * Variable: captureDocumentGesture
+ *
+ * Specifies if a gesture should be captured when it goes outside of the
+ * graph container. Default is true.
+ */
+mxGraphView.prototype.captureDocumentGesture = true;
+
+/**
+ * Variable: rendering
+ *
+ * Specifies if shapes should be created, updated and destroyed using the
+ * methods of <mxCellRenderer> in <graph>. Default is true.
+ */
+mxGraphView.prototype.rendering = true;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphView.prototype.graph = null;
+
+/**
+ * Variable: currentRoot
+ *
+ * <mxCell> that acts as the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.currentRoot = null;
+
+/**
+ * Variable: graphBounds
+ *
+ * <mxRectangle> that caches the scales, translated bounds of the current view.
+ */
+mxGraphView.prototype.graphBounds = null;
+
+/**
+ * Variable: scale
+ *
+ * Specifies the scale. Default is 1 (100%).
+ */
+mxGraphView.prototype.scale = 1;
+
+/**
+ * Variable: translate
+ *
+ * <mxPoint> that specifies the current translation. Default is a new
+ * empty <mxPoint>.
+ */
+mxGraphView.prototype.translate = null;
+
+/**
+ * Variable: updateStyle
+ *
+ * Specifies if the style should be updated in each validation step. If this
+ * is false then the style is only updated if the state is created or if the
+ * style of the cell was changed. Default is false.
+ */
+mxGraphView.prototype.updateStyle = false;
+
+/**
+ * Function: getGraphBounds
+ *
+ * Returns <graphBounds>.
+ */
+mxGraphView.prototype.getGraphBounds = function()
+{
+ return this.graphBounds;
+};
+
+/**
+ * Function: setGraphBounds
+ *
+ * Sets <graphBounds>.
+ */
+mxGraphView.prototype.setGraphBounds = function(value)
+{
+ this.graphBounds = value;
+};
+
+/**
+ * Function: getBounds
+ *
+ * Returns the bounds (on the screen) for the given array of <mxCells>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to return the bounds for.
+ */
+mxGraphView.prototype.getBounds = function(cells)
+{
+ var result = null;
+
+ if (cells != null && cells.length > 0)
+ {
+ var model = this.graph.getModel();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (model.isVertex(cells[i]) || model.isEdge(cells[i]))
+ {
+ var state = this.getState(cells[i]);
+
+ if (state != null)
+ {
+ if (result == null)
+ {
+ result = new mxRectangle(state.x, state.y,
+ state.width, state.height);
+ }
+ else
+ {
+ result.add(state);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: setCurrentRoot
+ *
+ * Sets and returns the current root and fires an <undo> event before
+ * calling <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that specifies the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.setCurrentRoot = function(root)
+{
+ if (this.currentRoot != root)
+ {
+ var change = new mxCurrentRootChange(this, root);
+ change.execute();
+ var edit = new mxUndoableEdit(this, false);
+ edit.add(change);
+ this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ this.graph.sizeDidChange();
+ }
+
+ return root;
+};
+
+/**
+ * Function: scaleAndTranslate
+ *
+ * Sets the scale and translation and fires a <scale> and <translate> event
+ * before calling <revalidate> followed by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * scale - Decimal value that specifies the new scale (1 is 100%).
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.scaleAndTranslate = function(scale, dx, dy)
+{
+ var previousScale = this.scale;
+ var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+
+ if (this.scale != scale || this.translate.x != dx || this.translate.y != dy)
+ {
+ this.scale = scale;
+
+ this.translate.x = dx;
+ this.translate.y = dy;
+
+ if (this.isEventsEnabled())
+ {
+ this.revalidate();
+ this.graph.sizeDidChange();
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE,
+ 'scale', scale, 'previousScale', previousScale,
+ 'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: getScale
+ *
+ * Returns the <scale>.
+ */
+mxGraphView.prototype.getScale = function()
+{
+ return this.scale;
+};
+
+/**
+ * Function: setScale
+ *
+ * Sets the scale and fires a <scale> event before calling <revalidate> followed
+ * by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * value - Decimal value that specifies the new scale (1 is 100%).
+ */
+mxGraphView.prototype.setScale = function(value)
+{
+ var previousScale = this.scale;
+
+ if (this.scale != value)
+ {
+ this.scale = value;
+
+ if (this.isEventsEnabled())
+ {
+ this.revalidate();
+ this.graph.sizeDidChange();
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SCALE,
+ 'scale', value, 'previousScale', previousScale));
+};
+
+/**
+ * Function: getTranslate
+ *
+ * Returns the <translate>.
+ */
+mxGraphView.prototype.getTranslate = function()
+{
+ return this.translate;
+};
+
+/**
+ * Function: setTranslate
+ *
+ * Sets the translation and fires a <translate> event before calling
+ * <revalidate> followed by <mxGraph.sizeDidChange>. The translation is the
+ * negative of the origin.
+ *
+ * Parameters:
+ *
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.setTranslate = function(dx, dy)
+{
+ var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+
+ if (this.translate.x != dx || this.translate.y != dy)
+ {
+ this.translate.x = dx;
+ this.translate.y = dy;
+
+ if (this.isEventsEnabled())
+ {
+ this.revalidate();
+ this.graph.sizeDidChange();
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.TRANSLATE,
+ 'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: refresh
+ *
+ * Clears the view if <currentRoot> is not null and revalidates.
+ */
+mxGraphView.prototype.refresh = function()
+{
+ if (this.currentRoot != null)
+ {
+ this.clear();
+ }
+
+ this.revalidate();
+};
+
+/**
+ * Function: revalidate
+ *
+ * Revalidates the complete view with all cell states.
+ */
+mxGraphView.prototype.revalidate = function()
+{
+ this.invalidate();
+ this.validate();
+};
+
+/**
+ * Function: clear
+ *
+ * Removes the state of the given cell and all descendants if the given
+ * cell is not the current root.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> for which the state should be removed. Default
+ * is the root of the model.
+ * force - Boolean indicating if the current root should be ignored for
+ * recursion.
+ */
+mxGraphView.prototype.clear = function(cell, force, recurse)
+{
+ var model = this.graph.getModel();
+ cell = cell || model.getRoot();
+ force = (force != null) ? force : false;
+ recurse = (recurse != null) ? recurse : true;
+
+ this.removeState(cell);
+
+ if (recurse && (force || cell != this.currentRoot))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.clear(model.getChildAt(cell, i), force);
+ }
+ }
+ else
+ {
+ this.invalidate(cell);
+ }
+};
+
+/**
+ * Function: invalidate
+ *
+ * Invalidates the state of the given cell, all its descendants and
+ * connected edges.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> to be invalidated. Default is the root of the
+ * model.
+ */
+mxGraphView.prototype.invalidate = function(cell, recurse, includeEdges, orderChanged)
+{
+ var model = this.graph.getModel();
+ cell = cell || model.getRoot();
+ recurse = (recurse != null) ? recurse : true;
+ includeEdges = (includeEdges != null) ? includeEdges : true;
+ orderChanged = (orderChanged != null) ? orderChanged : false;
+
+ var state = this.getState(cell);
+
+ if (state != null)
+ {
+ state.invalid = true;
+
+ if (orderChanged)
+ {
+ state.orderChanged = true;
+ }
+ }
+
+ // Recursively invalidates all descendants
+ if (recurse)
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ this.invalidate(child, recurse, includeEdges, orderChanged);
+ }
+ }
+
+ // Propagates invalidation to all connected edges
+ if (includeEdges)
+ {
+ var edgeCount = model.getEdgeCount(cell);
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ this.invalidate(model.getEdgeAt(cell, i), recurse, includeEdges);
+ }
+ }
+};
+
+/**
+ * Function: validate
+ *
+ * First validates all bounds and then validates all points recursively on
+ * all visible cells starting at the given cell. Finally the background
+ * is validated using <validateBackground>.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> to be used as the root of the validation.
+ * Default is <currentRoot> or the root of the model.
+ */
+mxGraphView.prototype.validate = function(cell)
+{
+ var t0 = mxLog.enter('mxGraphView.validate');
+ window.status = mxResources.get(this.updatingDocumentResource) ||
+ this.updatingDocumentResource;
+
+ cell = cell || ((this.currentRoot != null) ?
+ this.currentRoot :
+ this.graph.getModel().getRoot());
+ this.validateBounds(null, cell);
+ var graphBounds = this.validatePoints(null, cell);
+
+ if (graphBounds == null)
+ {
+ graphBounds = new mxRectangle();
+ }
+
+ this.setGraphBounds(graphBounds);
+ this.validateBackground();
+
+ window.status = mxResources.get(this.doneResource) ||
+ this.doneResource;
+ mxLog.leave('mxGraphView.validate', t0);
+};
+
+/**
+ * Function: createBackgroundPageShape
+ *
+ * Creates and returns the shape used as the background page.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that represents the bounds of the shape.
+ */
+mxGraphView.prototype.createBackgroundPageShape = function(bounds)
+{
+ return new mxRectangleShape(bounds, 'white', 'black');
+};
+
+/**
+ * Function: validateBackground
+ *
+ * Validates the background image.
+ */
+mxGraphView.prototype.validateBackground = function()
+{
+ var bg = this.graph.getBackgroundImage();
+
+ if (bg != null)
+ {
+ if (this.backgroundImage == null || this.backgroundImage.image != bg.src)
+ {
+ if (this.backgroundImage != null)
+ {
+ this.backgroundImage.destroy();
+ }
+
+ var bounds = new mxRectangle(0, 0, 1, 1);
+
+ this.backgroundImage = new mxImageShape(bounds, bg.src);
+ this.backgroundImage.dialect = this.graph.dialect;
+ this.backgroundImage.init(this.backgroundPane);
+ this.backgroundImage.redraw();
+ }
+
+ this.redrawBackgroundImage(this.backgroundImage, bg);
+ }
+ else if (this.backgroundImage != null)
+ {
+ this.backgroundImage.destroy();
+ this.backgroundImage = null;
+ }
+
+ if (this.graph.pageVisible)
+ {
+ var bounds = this.getBackgroundPageBounds();
+
+ if (this.backgroundPageShape == null)
+ {
+ this.backgroundPageShape = this.createBackgroundPageShape(bounds);
+ this.backgroundPageShape.scale = this.scale;
+ this.backgroundPageShape.isShadow = true;
+ this.backgroundPageShape.dialect = this.graph.dialect;
+ this.backgroundPageShape.init(this.backgroundPane);
+ this.backgroundPageShape.redraw();
+
+ // Adds listener for double click handling on background
+ mxEvent.addListener(this.backgroundPageShape.node, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.dblClick(evt);
+ })
+ );
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ // Adds basic listeners for graph event dispatching outside of the
+ // container and finishing the handling of a single gesture
+ mxEvent.addListener(this.backgroundPageShape.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+ })
+ );
+ mxEvent.addListener(this.backgroundPageShape.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ // Hides the tooltip if mouse is outside container
+ if (this.graph.tooltipHandler != null &&
+ this.graph.tooltipHandler.isHideOnHover())
+ {
+ this.graph.tooltipHandler.hide();
+ }
+
+ if (this.graph.isMouseDown &&
+ !mxEvent.isConsumed(evt))
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ mxEvent.addListener(this.backgroundPageShape.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt));
+ })
+ );
+ }
+ else
+ {
+ this.backgroundPageShape.scale = this.scale;
+ this.backgroundPageShape.bounds = bounds;
+ this.backgroundPageShape.redraw();
+ }
+ }
+ else if (this.backgroundPageShape != null)
+ {
+ this.backgroundPageShape.destroy();
+ this.backgroundPageShape = null;
+ }
+};
+
+/**
+ * Function: getBackgroundPageBounds
+ *
+ * Returns the bounds for the background page.
+ */
+mxGraphView.prototype.getBackgroundPageBounds = function()
+{
+ var fmt = this.graph.pageFormat;
+ var ps = this.scale * this.graph.pageScale;
+ var bounds = new mxRectangle(this.scale * this.translate.x, this.scale * this.translate.y,
+ fmt.width * ps, fmt.height * ps);
+
+ return bounds;
+};
+
+/**
+ * Function: redrawBackgroundImage
+ *
+ * Updates the bounds and redraws the background image.
+ *
+ * Example:
+ *
+ * If the background image should not be scaled, this can be replaced with
+ * the following.
+ *
+ * (code)
+ * mxGraphView.prototype.redrawBackground = function(backgroundImage, bg)
+ * {
+ * backgroundImage.bounds.x = this.translate.x;
+ * backgroundImage.bounds.y = this.translate.y;
+ * backgroundImage.bounds.width = bg.width;
+ * backgroundImage.bounds.height = bg.height;
+ *
+ * backgroundImage.redraw();
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * backgroundImage - <mxImageShape> that represents the background image.
+ * bg - <mxImage> that specifies the image and its dimensions.
+ */
+mxGraphView.prototype.redrawBackgroundImage = function(backgroundImage, bg)
+{
+ backgroundImage.scale = this.scale;
+ backgroundImage.bounds.x = this.scale * this.translate.x;
+ backgroundImage.bounds.y = this.scale * this.translate.y;
+ backgroundImage.bounds.width = this.scale * bg.width;
+ backgroundImage.bounds.height = this.scale * bg.height;
+
+ backgroundImage.redraw();
+};
+
+/**
+ * Function: validateBounds
+ *
+ * Validates the bounds of the given parent's child using the given parent
+ * state as the origin for the child. The validation is carried out
+ * recursively for all non-collapsed descendants.
+ *
+ * Parameters:
+ *
+ * parentState - <mxCellState> for the given parent.
+ * cell - <mxCell> for which the bounds in the state should be updated.
+ */
+mxGraphView.prototype.validateBounds = function(parentState, cell)
+{
+ var model = this.graph.getModel();
+ var state = this.getState(cell, true);
+
+ if (state != null && state.invalid)
+ {
+ if (!this.graph.isCellVisible(cell))
+ {
+ this.removeState(cell);
+ }
+
+ // Updates the cell state's origin
+ else if (cell != this.currentRoot && parentState != null)
+ {
+ state.absoluteOffset.x = 0;
+ state.absoluteOffset.y = 0;
+ state.origin.x = parentState.origin.x;
+ state.origin.y = parentState.origin.y;
+ var geo = this.graph.getCellGeometry(cell);
+
+ if (geo != null)
+ {
+ if (!model.isEdge(cell))
+ {
+ var offset = geo.offset || this.EMPTY_POINT;
+
+ if (geo.relative)
+ {
+ state.origin.x += geo.x * parentState.width /
+ this.scale + offset.x;
+ state.origin.y += geo.y * parentState.height /
+ this.scale + offset.y;
+ }
+ else
+ {
+ state.absoluteOffset.x = this.scale * offset.x;
+ state.absoluteOffset.y = this.scale * offset.y;
+ state.origin.x += geo.x;
+ state.origin.y += geo.y;
+ }
+ }
+
+ // Updates cell state's bounds
+ state.x = this.scale * (this.translate.x + state.origin.x);
+ state.y = this.scale * (this.translate.y + state.origin.y);
+ state.width = this.scale * geo.width;
+ state.height = this.scale * geo.height;
+
+ if (model.isVertex(cell))
+ {
+ this.updateVertexLabelOffset(state);
+ }
+ }
+ }
+
+ // Applies child offset to origin
+ var offset = this.graph.getChildOffsetForCell(cell);
+
+ if (offset != null)
+ {
+ state.origin.x += offset.x;
+ state.origin.y += offset.y;
+ }
+ }
+
+ // Recursively validates the child bounds
+ if (state != null && (!this.graph.isCellCollapsed(cell) ||
+ cell == this.currentRoot))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ this.validateBounds(state, child);
+ }
+ }
+};
+
+/**
+ * Function: updateVertexLabelOffset
+ *
+ * Updates the absoluteOffset of the given vertex cell state. This takes
+ * into account the label position styles.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateVertexLabelOffset = function(state)
+{
+ var horizontal = mxUtils.getValue(state.style,
+ mxConstants.STYLE_LABEL_POSITION,
+ mxConstants.ALIGN_CENTER);
+
+ if (horizontal == mxConstants.ALIGN_LEFT)
+ {
+ state.absoluteOffset.x -= state.width;
+ }
+ else if (horizontal == mxConstants.ALIGN_RIGHT)
+ {
+ state.absoluteOffset.x += state.width;
+ }
+
+ var vertical = mxUtils.getValue(state.style,
+ mxConstants.STYLE_VERTICAL_LABEL_POSITION,
+ mxConstants.ALIGN_MIDDLE);
+
+ if (vertical == mxConstants.ALIGN_TOP)
+ {
+ state.absoluteOffset.y -= state.height;
+ }
+ else if (vertical == mxConstants.ALIGN_BOTTOM)
+ {
+ state.absoluteOffset.y += state.height;
+ }
+};
+
+/**
+ * Function: validatePoints
+ *
+ * Validates the points for the state of the given cell recursively if the
+ * cell is not collapsed and returns the bounding box of all visited states
+ * as an <mxRectangle>.
+ *
+ * Parameters:
+ *
+ * parentState - <mxCellState> for the parent cell.
+ * cell - <mxCell> whose points in the state should be updated.
+ */
+mxGraphView.prototype.validatePoints = function(parentState, cell)
+{
+ var model = this.graph.getModel();
+ var state = this.getState(cell);
+ var bbox = null;
+
+ if (state != null)
+ {
+ if (state.invalid)
+ {
+ var geo = this.graph.getCellGeometry(cell);
+
+ if (geo != null && model.isEdge(cell))
+ {
+ // Updates the points on the source terminal if its an edge
+ var source = this.getState(this.getVisibleTerminal(cell, true));
+ state.setVisibleTerminalState(source, true);
+
+ if (source != null && model.isEdge(source.cell) &&
+ !model.isAncestor(source.cell, cell))
+ {
+ var tmp = this.getState(model.getParent(source.cell));
+ this.validatePoints(tmp, source.cell);
+ }
+
+ // Updates the points on the target terminal if its an edge
+ var target = this.getState(this.getVisibleTerminal(cell, false));
+ state.setVisibleTerminalState(target, false);
+
+ if (target != null && model.isEdge(target.cell) &&
+ !model.isAncestor(target.cell, cell))
+ {
+ var tmp = this.getState(model.getParent(target.cell));
+ this.validatePoints(tmp, target.cell);
+ }
+
+ this.updateFixedTerminalPoints(state, source, target);
+ this.updatePoints(state, geo.points, source, target);
+ this.updateFloatingTerminalPoints(state, source, target);
+ this.updateEdgeBounds(state);
+ this.updateEdgeLabelOffset(state);
+ }
+ else if (geo != null && geo.relative && parentState != null &&
+ model.isEdge(parentState.cell))
+ {
+ var origin = this.getPoint(parentState, geo);
+
+ if (origin != null)
+ {
+ state.x = origin.x;
+ state.y = origin.y;
+
+ origin.x = (origin.x / this.scale) - this.translate.x;
+ origin.y = (origin.y / this.scale) - this.translate.y;
+ state.origin = origin;
+
+ this.childMoved(parentState, state);
+ }
+ }
+
+ state.invalid = false;
+
+ if (cell != this.currentRoot)
+ {
+ // NOTE: Label bounds currently ignored if rendering is false
+ this.graph.cellRenderer.redraw(state, false, this.isRendering());
+ }
+ }
+
+ if (model.isEdge(cell) || model.isVertex(cell))
+ {
+ if (state.shape != null && state.shape.boundingBox != null)
+ {
+ bbox = state.shape.boundingBox.clone();
+ }
+
+ if (state.text != null && !this.graph.isLabelClipped(state.cell))
+ {
+ // Adds label bounding box to graph bounds
+ if (state.text.boundingBox != null)
+ {
+ if (bbox != null)
+ {
+ bbox.add(state.text.boundingBox);
+ }
+ else
+ {
+ bbox = state.text.boundingBox.clone();
+ }
+ }
+ }
+ }
+ }
+
+ if (state != null && (!this.graph.isCellCollapsed(cell) ||
+ cell == this.currentRoot))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ var bounds = this.validatePoints(state, child);
+
+ if (bounds != null)
+ {
+ if (bbox == null)
+ {
+ bbox = bounds;
+ }
+ else
+ {
+ bbox.add(bounds);
+ }
+ }
+ }
+ }
+
+ return bbox;
+};
+
+/**
+ * Function: childMoved
+ *
+ * Invoked when a child state was moved as a result of late evaluation
+ * of its position. This is invoked for relative edge children whose
+ * position can only be determined after the points of the parent edge
+ * are updated in validatePoints, and validates the bounds of all
+ * descendants of the child using validateBounds.
+ *
+ * Parameters:
+ *
+ * parent - <mxCellState> that represents the parent state.
+ * child - <mxCellState> that represents the child state.
+ */
+mxGraphView.prototype.childMoved = function(parent, child)
+{
+ var cell = child.cell;
+
+ // Children of relative edge children need to validate
+ // their bounds after their parent state was updated
+ if (!this.graph.isCellCollapsed(cell) || cell == this.currentRoot)
+ {
+ var model = this.graph.getModel();
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.validateBounds(child, model.getChildAt(cell, i));
+ }
+ }
+};
+
+/**
+ * Function: updateFixedTerminalPoints
+ *
+ * Sets the initial absolute terminal points in the given state before the edge
+ * style is computed.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose initial terminal points should be updated.
+ * source - <mxCellState> which represents the source terminal.
+ * target - <mxCellState> which represents the target terminal.
+ */
+mxGraphView.prototype.updateFixedTerminalPoints = function(edge, source, target)
+{
+ this.updateFixedTerminalPoint(edge, source, true,
+ this.graph.getConnectionConstraint(edge, source, true));
+ this.updateFixedTerminalPoint(edge, target, false,
+ this.graph.getConnectionConstraint(edge, target, false));
+};
+
+/**
+ * Function: updateFixedTerminalPoint
+ *
+ * Sets the fixed source or target terminal point on the given edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose terminal point should be updated.
+ * terminal - <mxCellState> which represents the actual terminal.
+ * source - Boolean that specifies if the terminal is the source.
+ * constraint - <mxConnectionConstraint> that specifies the connection.
+ */
+mxGraphView.prototype.updateFixedTerminalPoint = function(edge, terminal, source, constraint)
+{
+ var pt = null;
+
+ if (constraint != null)
+ {
+ pt = this.graph.getConnectionPoint(terminal, constraint);
+ }
+
+ if (pt == null && terminal == null)
+ {
+ var s = this.scale;
+ var tr = this.translate;
+ var orig = edge.origin;
+ var geo = this.graph.getCellGeometry(edge.cell);
+ pt = geo.getTerminalPoint(source);
+
+ if (pt != null)
+ {
+ pt = new mxPoint(s * (tr.x + pt.x + orig.x),
+ s * (tr.y + pt.y + orig.y));
+ }
+ }
+
+ edge.setAbsoluteTerminalPoint(pt, source);
+};
+
+/**
+ * Function: updatePoints
+ *
+ * Updates the absolute points in the given state using the specified array
+ * of <mxPoints> as the relative points.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose absolute points should be updated.
+ * points - Array of <mxPoints> that constitute the relative points.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updatePoints = function(edge, points, source, target)
+{
+ if (edge != null)
+ {
+ var pts = [];
+ pts.push(edge.absolutePoints[0]);
+ var edgeStyle = this.getEdgeStyle(edge, points, source, target);
+
+ if (edgeStyle != null)
+ {
+ var src = this.getTerminalPort(edge, source, true);
+ var trg = this.getTerminalPort(edge, target, false);
+
+ edgeStyle(edge, src, trg, points, pts);
+ }
+ else if (points != null)
+ {
+ for (var i = 0; i < points.length; i++)
+ {
+ if (points[i] != null)
+ {
+ var pt = mxUtils.clone(points[i]);
+ pts.push(this.transformControlPoint(edge, pt));
+ }
+ }
+ }
+
+ var tmp = edge.absolutePoints;
+ pts.push(tmp[tmp.length-1]);
+
+ edge.absolutePoints = pts;
+ }
+};
+
+/**
+ * Function: transformControlPoint
+ *
+ * Transforms the given control point to an absolute point.
+ */
+mxGraphView.prototype.transformControlPoint = function(state, pt)
+{
+ var orig = state.origin;
+
+ return new mxPoint(this.scale * (pt.x + this.translate.x + orig.x),
+ this.scale * (pt.y + this.translate.y + orig.y));
+};
+
+/**
+ * Function: getEdgeStyle
+ *
+ * Returns the edge style function to be used to render the given edge
+ * state.
+ */
+mxGraphView.prototype.getEdgeStyle = function(edge, points, source, target)
+{
+ var edgeStyle = (source != null && source == target) ?
+ mxUtils.getValue(edge.style, mxConstants.STYLE_LOOP,
+ this.graph.defaultLoopStyle) :
+ (!mxUtils.getValue(edge.style,
+ mxConstants.STYLE_NOEDGESTYLE, false) ?
+ edge.style[mxConstants.STYLE_EDGE] :
+ null);
+
+ // Converts string values to objects
+ if (typeof(edgeStyle) == "string")
+ {
+ var tmp = mxStyleRegistry.getValue(edgeStyle);
+
+ if (tmp == null && this.isAllowEval())
+ {
+ tmp = mxUtils.eval(edgeStyle);
+ }
+
+ edgeStyle = tmp;
+ }
+
+ if (typeof(edgeStyle) == "function")
+ {
+ return edgeStyle;
+ }
+
+ return null;
+};
+
+/**
+ * Function: updateFloatingTerminalPoints
+ *
+ * Updates the terminal points in the given state after the edge style was
+ * computed for the edge.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose terminal points should be updated.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoints = function(state, source, target)
+{
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length - 1];
+
+ if (pe == null && target != null)
+ {
+ this.updateFloatingTerminalPoint(state, target, source, false);
+ }
+
+ if (p0 == null && source != null)
+ {
+ this.updateFloatingTerminalPoint(state, source, target, true);
+ }
+};
+
+/**
+ * Function: updateFloatingTerminalPoint
+ *
+ * Updates the absolute terminal point in the given state for the given
+ * start and end state, where start is the source if source is true.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose terminal point should be updated.
+ * start - <mxCellState> for the terminal on "this" side of the edge.
+ * end - <mxCellState> for the terminal on the other side of the edge.
+ * source - Boolean indicating if start is the source terminal state.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source)
+{
+ start = this.getTerminalPort(edge, start, source);
+ var next = this.getNextPoint(edge, end, source);
+
+ var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));
+ var center = new mxPoint(start.getCenterX(), start.getCenterY());
+
+ if (alpha != 0)
+ {
+ var cos = Math.cos(-alpha);
+ var sin = Math.sin(-alpha);
+ next = mxUtils.getRotatedPoint(next, cos, sin, center);
+ }
+
+ var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+ border += parseFloat(edge.style[(source) ?
+ mxConstants.STYLE_SOURCE_PERIMETER_SPACING :
+ mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0);
+ var pt = this.getPerimeterPoint(start, next, this.graph.isOrthogonal(edge), border);
+
+ if (alpha != 0)
+ {
+ var cos = Math.cos(alpha);
+ var sin = Math.sin(alpha);
+ pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
+ }
+
+ edge.setAbsoluteTerminalPoint(pt, source);
+};
+
+/**
+ * Function: getTerminalPort
+ *
+ * Returns an <mxCellState> that represents the source or target terminal or
+ * port for the given edge.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the state of the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the given terminal is the source terminal.
+ */
+mxGraphView.prototype.getTerminalPort = function(state, terminal, source)
+{
+ var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+ mxConstants.STYLE_TARGET_PORT;
+ var id = mxUtils.getValue(state.style, key);
+
+ if (id != null)
+ {
+ var tmp = this.getState(this.graph.getModel().getCell(id));
+
+ // Only uses ports where a cell state exists
+ if (tmp != null)
+ {
+ terminal = tmp;
+ }
+ }
+
+ return terminal;
+};
+
+/**
+ * Function: getPerimeterPoint
+ *
+ * Returns an <mxPoint> that defines the location of the intersection point between
+ * the perimeter and the line between the center of the shape and the given point.
+ *
+ * Parameters:
+ *
+ * terminal - <mxCellState> for the source or target terminal.
+ * next - <mxPoint> that lies outside of the given terminal.
+ * orthogonal - Boolean that specifies if the orthogonal projection onto
+ * the perimeter should be returned. If this is false then the intersection
+ * of the perimeter and the line between the next and the center point is
+ * returned.
+ * border - Optional border between the perimeter and the shape.
+ */
+mxGraphView.prototype.getPerimeterPoint = function(terminal, next, orthogonal, border)
+{
+ var point = null;
+
+ if (terminal != null)
+ {
+ var perimeter = this.getPerimeterFunction(terminal);
+
+ if (perimeter != null && next != null)
+ {
+ var bounds = this.getPerimeterBounds(terminal, border);
+
+ if (bounds.width > 0 || bounds.height > 0)
+ {
+ point = perimeter(bounds, terminal, next, orthogonal);
+ }
+ }
+
+ if (point == null)
+ {
+ point = this.getPoint(terminal);
+ }
+ }
+
+ return point;
+};
+
+/**
+ * Function: getRoutingCenterX
+ *
+ * Returns the x-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterX = function (state)
+{
+ var f = (state.style != null) ? parseFloat(state.style
+ [mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;
+
+ return state.getCenterX() + f * state.width;
+};
+
+/**
+ * Function: getRoutingCenterY
+ *
+ * Returns the y-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterY = function (state)
+{
+ var f = (state.style != null) ? parseFloat(state.style
+ [mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;
+
+ return state.getCenterY() + f * state.height;
+};
+
+/**
+ * Function: getPerimeterBounds
+ *
+ * Returns the perimeter bounds for the given terminal, edge pair as an
+ * <mxRectangle>.
+ *
+ * If you have a model where each terminal has a relative child that should
+ * act as the graphical endpoint for a connection from/to the terminal, then
+ * this method can be replaced as follows:
+ *
+ * (code)
+ * var oldGetPerimeterBounds = mxGraphView.prototype.getPerimeterBounds;
+ * mxGraphView.prototype.getPerimeterBounds = function(terminal, edge, isSource)
+ * {
+ * var model = this.graph.getModel();
+ * var childCount = model.getChildCount(terminal.cell);
+ *
+ * if (childCount > 0)
+ * {
+ * var child = model.getChildAt(terminal.cell, 0);
+ * var geo = model.getGeometry(child);
+ *
+ * if (geo != null &&
+ * geo.relative)
+ * {
+ * var state = this.getState(child);
+ *
+ * if (state != null)
+ * {
+ * terminal = state;
+ * }
+ * }
+ * }
+ *
+ * return oldGetPerimeterBounds.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * terminal - <mxCellState> that represents the terminal.
+ * border - Number that adds a border between the shape and the perimeter.
+ */
+mxGraphView.prototype.getPerimeterBounds = function(terminal, border)
+{
+ border = (border != null) ? border : 0;
+
+ if (terminal != null)
+ {
+ border += parseFloat(terminal.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+ }
+
+ return terminal.getPerimeterBounds(border * this.scale);
+};
+
+/**
+ * Function: getPerimeterFunction
+ *
+ * Returns the perimeter function for the given state.
+ */
+mxGraphView.prototype.getPerimeterFunction = function(state)
+{
+ var perimeter = state.style[mxConstants.STYLE_PERIMETER];
+
+ // Converts string values to objects
+ if (typeof(perimeter) == "string")
+ {
+ var tmp = mxStyleRegistry.getValue(perimeter);
+
+ if (tmp == null && this.isAllowEval())
+ {
+ tmp = mxUtils.eval(perimeter);
+ }
+
+ perimeter = tmp;
+ }
+
+ if (typeof(perimeter) == "function")
+ {
+ return perimeter;
+ }
+
+ return null;
+};
+
+/**
+ * Function: getNextPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> that represents the edge.
+ * opposite - <mxCellState> that represents the opposite terminal.
+ * source - Boolean indicating if the next point for the source or target
+ * should be returned.
+ */
+mxGraphView.prototype.getNextPoint = function(edge, opposite, source)
+{
+ var pts = edge.absolutePoints;
+ var point = null;
+
+ if (pts != null && (source || pts.length > 2 || opposite == null))
+ {
+ var count = pts.length;
+ point = pts[(source) ? Math.min(1, count - 1) : Math.max(0, count - 2)];
+ }
+
+ if (point == null && opposite != null)
+ {
+ point = new mxPoint(opposite.getCenterX(), opposite.getCenterY());
+ }
+
+ return point;
+};
+
+/**
+ * Function: getVisibleTerminal
+ *
+ * Returns the nearest ancestor terminal that is visible. The edge appears
+ * to be connected to this terminal on the display. The result of this method
+ * is cached in <mxCellState.getVisibleTerminalState>.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose visible terminal should be returned.
+ * source - Boolean that specifies if the source or target terminal
+ * should be returned.
+ */
+mxGraphView.prototype.getVisibleTerminal = function(edge, source)
+{
+ var model = this.graph.getModel();
+ var result = model.getTerminal(edge, source);
+ var best = result;
+
+ while (result != null && result != this.currentRoot)
+ {
+ if (!this.graph.isCellVisible(best) || this.graph.isCellCollapsed(result))
+ {
+ best = result;
+ }
+
+ result = model.getParent(result);
+ }
+
+ // Checks if the result is not a layer
+ if (model.getParent(best) == model.getRoot())
+ {
+ best = null;
+ }
+
+ return best;
+};
+
+/**
+ * Function: updateEdgeBounds
+ *
+ * Updates the given state using the bounding box of the absolute points.
+ * Also updates <mxCellState.terminalDistance>, <mxCellState.length> and
+ * <mxCellState.segments>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose bounds should be updated.
+ */
+mxGraphView.prototype.updateEdgeBounds = function(state)
+{
+ var points = state.absolutePoints;
+ state.length = 0;
+
+ if (points != null && points.length > 0)
+ {
+ var p0 = points[0];
+ var pe = points[points.length - 1];
+
+ if (p0 == null || pe == null)
+ {
+ // Drops the edge state if the edge is not the root
+ if (state.cell != this.currentRoot)
+ {
+ // Note: This condition normally occurs if a connected edge has a
+ // null-terminal, ie. edge.source == null or edge.target == null,
+ // and no corresponding terminal point defined, which happens for
+ // example if the terminal-id was not resolved at cell decoding time.
+ this.clear(state.cell, true);
+ }
+ }
+ else
+ {
+ if (p0.x != pe.x || p0.y != pe.y)
+ {
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ state.terminalDistance = Math.sqrt(dx * dx + dy * dy);
+ }
+ else
+ {
+ state.terminalDistance = 0;
+ }
+
+ var length = 0;
+ var segments = [];
+ var pt = p0;
+
+ if (pt != null)
+ {
+ var minX = pt.x;
+ var minY = pt.y;
+ var maxX = minX;
+ var maxY = minY;
+
+ for (var i = 1; i < points.length; i++)
+ {
+ var tmp = points[i];
+
+ if (tmp != null)
+ {
+ var dx = pt.x - tmp.x;
+ var dy = pt.y - tmp.y;
+
+ var segment = Math.sqrt(dx * dx + dy * dy);
+ segments.push(segment);
+ length += segment;
+
+ pt = tmp;
+
+ minX = Math.min(pt.x, minX);
+ minY = Math.min(pt.y, minY);
+ maxX = Math.max(pt.x, maxX);
+ maxY = Math.max(pt.y, maxY);
+ }
+ }
+
+ state.length = length;
+ state.segments = segments;
+
+ var markerSize = 1; // TODO: include marker size
+
+ state.x = minX;
+ state.y = minY;
+ state.width = Math.max(markerSize, maxX - minX);
+ state.height = Math.max(markerSize, maxY - minY);
+ }
+ }
+ }
+};
+
+/**
+ * Function: getPoint
+ *
+ * Returns the absolute point on the edge for the given relative
+ * <mxGeometry> as an <mxPoint>. The edge is represented by the given
+ * <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the state of the parent edge.
+ * geometry - <mxGeometry> that represents the relative location.
+ */
+mxGraphView.prototype.getPoint = function(state, geometry)
+{
+ var x = state.getCenterX();
+ var y = state.getCenterY();
+
+ if (state.segments != null && (geometry == null || geometry.relative))
+ {
+ var gx = (geometry != null) ? geometry.x / 2 : 0;
+ var pointCount = state.absolutePoints.length;
+ var dist = (gx + 0.5) * state.length;
+ var segment = state.segments[0];
+ var length = 0;
+ var index = 1;
+
+ while (dist > length + segment && index < pointCount-1)
+ {
+ length += segment;
+ segment = state.segments[index++];
+ }
+
+ var factor = (segment == 0) ? 0 : (dist - length) / segment;
+ var p0 = state.absolutePoints[index-1];
+ var pe = state.absolutePoints[index];
+
+ if (p0 != null && pe != null)
+ {
+ var gy = 0;
+ var offsetX = 0;
+ var offsetY = 0;
+
+ if (geometry != null)
+ {
+ gy = geometry.y;
+ var offset = geometry.offset;
+
+ if (offset != null)
+ {
+ offsetX = offset.x;
+ offsetY = offset.y;
+ }
+ }
+
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ var nx = (segment == 0) ? 0 : dy / segment;
+ var ny = (segment == 0) ? 0 : dx / segment;
+
+ x = p0.x + dx * factor + (nx * gy + offsetX) * this.scale;
+ y = p0.y + dy * factor - (ny * gy - offsetY) * this.scale;
+ }
+ }
+ else if (geometry != null)
+ {
+ var offset = geometry.offset;
+
+ if (offset != null)
+ {
+ x += offset.x;
+ y += offset.y;
+ }
+ }
+
+ return new mxPoint(x, y);
+};
+
+/**
+ * Function: getRelativePoint
+ *
+ * Gets the relative point that describes the given, absolute label
+ * position for the given edge state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the state of the parent edge.
+ * x - Specifies the x-coordinate of the absolute label location.
+ * y - Specifies the y-coordinate of the absolute label location.
+ */
+mxGraphView.prototype.getRelativePoint = function(edgeState, x, y)
+{
+ var model = this.graph.getModel();
+ var geometry = model.getGeometry(edgeState.cell);
+
+ if (geometry != null)
+ {
+ var pointCount = edgeState.absolutePoints.length;
+
+ if (geometry.relative && pointCount > 1)
+ {
+ var totalLength = edgeState.length;
+ var segments = edgeState.segments;
+
+ // Works which line segment the point of the label is closest to
+ var p0 = edgeState.absolutePoints[0];
+ var pe = edgeState.absolutePoints[1];
+ var minDist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+ var index = 0;
+ var tmp = 0;
+ var length = 0;
+
+ for (var i = 2; i < pointCount; i++)
+ {
+ tmp += segments[i - 2];
+ pe = edgeState.absolutePoints[i];
+ var dist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+ if (dist <= minDist)
+ {
+ minDist = dist;
+ index = i - 1;
+ length = tmp;
+ }
+
+ p0 = pe;
+ }
+
+ var seg = segments[index];
+ p0 = edgeState.absolutePoints[index];
+ pe = edgeState.absolutePoints[index + 1];
+
+ var x2 = p0.x;
+ var y2 = p0.y;
+
+ var x1 = pe.x;
+ var y1 = pe.y;
+
+ var px = x;
+ var py = y;
+
+ var xSegment = x2 - x1;
+ var ySegment = y2 - y1;
+
+ px -= x1;
+ py -= y1;
+ var projlenSq = 0;
+
+ px = xSegment - px;
+ py = ySegment - py;
+ var dotprod = px * xSegment + py * ySegment;
+
+ if (dotprod <= 0.0)
+ {
+ projlenSq = 0;
+ }
+ else
+ {
+ projlenSq = dotprod * dotprod
+ / (xSegment * xSegment + ySegment * ySegment);
+ }
+
+ var projlen = Math.sqrt(projlenSq);
+
+ if (projlen > seg)
+ {
+ projlen = seg;
+ }
+
+ var yDistance = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, pe
+ .x, pe.y, x, y));
+ var direction = mxUtils.relativeCcw(p0.x, p0.y, pe.x, pe.y, x, y);
+
+ if (direction == -1)
+ {
+ yDistance = -yDistance;
+ }
+
+ // Constructs the relative point for the label
+ return new mxPoint(((totalLength / 2 - length - projlen) / totalLength) * -2,
+ yDistance / this.scale);
+ }
+ }
+
+ return new mxPoint();
+};
+
+/**
+ * Function: updateEdgeLabelOffset
+ *
+ * Updates <mxCellState.absoluteOffset> for the given state. The absolute
+ * offset is normally used for the position of the edge label. Is is
+ * calculated from the geometry as an absolute offset from the center
+ * between the two endpoints if the geometry is absolute, or as the
+ * relative distance between the center along the line and the absolute
+ * orthogonal distance if the geometry is relative.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateEdgeLabelOffset = function(state)
+{
+ var points = state.absolutePoints;
+
+ state.absoluteOffset.x = state.getCenterX();
+ state.absoluteOffset.y = state.getCenterY();
+
+ if (points != null && points.length > 0 && state.segments != null)
+ {
+ var geometry = this.graph.getCellGeometry(state.cell);
+
+ if (geometry.relative)
+ {
+ var offset = this.getPoint(state, geometry);
+
+ if (offset != null)
+ {
+ state.absoluteOffset = offset;
+ }
+ }
+ else
+ {
+ var p0 = points[0];
+ var pe = points[points.length - 1];
+
+ if (p0 != null && pe != null)
+ {
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ var x0 = 0;
+ var y0 = 0;
+
+ var off = geometry.offset;
+
+ if (off != null)
+ {
+ x0 = off.x;
+ y0 = off.y;
+ }
+
+ var x = p0.x + dx / 2 + x0 * this.scale;
+ var y = p0.y + dy / 2 + y0 * this.scale;
+
+ state.absoluteOffset.x = x;
+ state.absoluteOffset.y = y;
+ }
+ }
+ }
+};
+
+/**
+ * Function: getState
+ *
+ * Returns the <mxCellState> for the given cell. If create is true, then
+ * the state is created if it does not yet exist.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the <mxCellState> should be returned.
+ * create - Optional boolean indicating if a new state should be created
+ * if it does not yet exist. Default is false.
+ */
+mxGraphView.prototype.getState = function(cell, create)
+{
+ create = create || false;
+ var state = null;
+
+ if (cell != null)
+ {
+ state = this.states.get(cell);
+
+ if (this.graph.isCellVisible(cell))
+ {
+ if (state == null && create && this.graph.isCellVisible(cell))
+ {
+ state = this.createState(cell);
+ this.states.put(cell, state);
+ }
+ else if (create && state != null && this.updateStyle)
+ {
+ state.style = this.graph.getCellStyle(cell);
+ }
+ }
+ }
+
+ return state;
+};
+
+/**
+ * Function: isRendering
+ *
+ * Returns <rendering>.
+ */
+mxGraphView.prototype.isRendering = function()
+{
+ return this.rendering;
+};
+
+/**
+ * Function: setRendering
+ *
+ * Sets <rendering>.
+ */
+mxGraphView.prototype.setRendering = function(value)
+{
+ this.rendering = value;
+};
+
+/**
+ * Function: isAllowEval
+ *
+ * Returns <allowEval>.
+ */
+mxGraphView.prototype.isAllowEval = function()
+{
+ return this.allowEval;
+};
+
+/**
+ * Function: setAllowEval
+ *
+ * Sets <allowEval>.
+ */
+mxGraphView.prototype.setAllowEval = function(value)
+{
+ this.allowEval = value;
+};
+
+/**
+ * Function: getStates
+ *
+ * Returns <states>.
+ */
+mxGraphView.prototype.getStates = function()
+{
+ return this.states;
+};
+
+/**
+ * Function: setStates
+ *
+ * Sets <states>.
+ */
+mxGraphView.prototype.setStates = function(value)
+{
+ this.states = value;
+};
+
+/**
+ * Function: getCellStates
+ *
+ * Returns the <mxCellStates> for the given array of <mxCells>. The array
+ * contains all states that are not null, that is, the returned array may
+ * have less elements than the given array. If no argument is given, then
+ * this returns <states>.
+ */
+mxGraphView.prototype.getCellStates = function(cells)
+{
+ if (cells == null)
+ {
+ return this.states;
+ }
+ else
+ {
+ var result = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var state = this.getState(cells[i]);
+
+ if (state != null)
+ {
+ result.push(state);
+ }
+ }
+
+ return result;
+ }
+};
+
+/**
+ * Function: removeState
+ *
+ * Removes and returns the <mxCellState> for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the <mxCellState> should be removed.
+ */
+mxGraphView.prototype.removeState = function(cell)
+{
+ var state = null;
+
+ if (cell != null)
+ {
+ state = this.states.remove(cell);
+
+ if (state != null)
+ {
+ this.graph.cellRenderer.destroy(state);
+ state.destroy();
+ }
+ }
+
+ return state;
+};
+
+/**
+ * Function: createState
+ *
+ * Creates and returns an <mxCellState> for the given cell and initializes
+ * it using <mxCellRenderer.initialize>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which a new <mxCellState> should be created.
+ */
+mxGraphView.prototype.createState = function(cell)
+{
+ var style = this.graph.getCellStyle(cell);
+ var state = new mxCellState(this, cell, style);
+ this.graph.cellRenderer.initialize(state, this.isRendering());
+
+ return state;
+};
+
+/**
+ * Function: getCanvas
+ *
+ * Returns the DOM node that contains the background-, draw- and
+ * overlaypane.
+ */
+mxGraphView.prototype.getCanvas = function()
+{
+ return this.canvas;
+};
+
+/**
+ * Function: getBackgroundPane
+ *
+ * Returns the DOM node that represents the background layer.
+ */
+mxGraphView.prototype.getBackgroundPane = function()
+{
+ return this.backgroundPane;
+};
+
+/**
+ * Function: getDrawPane
+ *
+ * Returns the DOM node that represents the main drawing layer.
+ */
+mxGraphView.prototype.getDrawPane = function()
+{
+ return this.drawPane;
+};
+
+/**
+ * Function: getOverlayPane
+ *
+ * Returns the DOM node that represents the topmost drawing layer.
+ */
+mxGraphView.prototype.getOverlayPane = function()
+{
+ return this.overlayPane;
+};
+
+/**
+ * Function: isContainerEvent
+ *
+ * Returns true if the event origin is one of the drawing panes or
+ * containers of the view.
+ */
+mxGraphView.prototype.isContainerEvent = function(evt)
+{
+ var source = mxEvent.getSource(evt);
+
+ return (source == this.graph.container ||
+ source.parentNode == this.backgroundPane ||
+ (source.parentNode != null &&
+ source.parentNode.parentNode == this.backgroundPane) ||
+ source == this.canvas.parentNode ||
+ source == this.canvas ||
+ source == this.backgroundPane ||
+ source == this.drawPane ||
+ source == this.overlayPane);
+};
+
+/**
+ * Function: isScrollEvent
+ *
+ * Returns true if the event origin is one of the scrollbars of the
+ * container in IE. Such events are ignored.
+ */
+ mxGraphView.prototype.isScrollEvent = function(evt)
+{
+ var offset = mxUtils.getOffset(this.graph.container);
+ var pt = new mxPoint(evt.clientX - offset.x, evt.clientY - offset.y);
+
+ var outWidth = this.graph.container.offsetWidth;
+ var inWidth = this.graph.container.clientWidth;
+
+ if (outWidth > inWidth && pt.x > inWidth + 2 && pt.x <= outWidth)
+ {
+ return true;
+ }
+
+ var outHeight = this.graph.container.offsetHeight;
+ var inHeight = this.graph.container.clientHeight;
+
+ if (outHeight > inHeight && pt.y > inHeight + 2 && pt.y <= outHeight)
+ {
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the graph event dispatch loop for the specified container
+ * and invokes <create> to create the required DOM nodes for the display.
+ */
+mxGraphView.prototype.init = function()
+{
+ this.installListeners();
+
+ // Creates the DOM nodes for the respective display dialect
+ var graph = this.graph;
+
+ if (graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.createSvg();
+ }
+ else if (graph.dialect == mxConstants.DIALECT_VML)
+ {
+ this.createVml();
+ }
+ else
+ {
+ this.createHtml();
+ }
+};
+
+/**
+ * Function: installListeners
+ *
+ * Installs the required listeners in the container.
+ */
+mxGraphView.prototype.installListeners = function()
+{
+ var graph = this.graph;
+ var container = graph.container;
+
+ if (container != null)
+ {
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ // Adds basic listeners for graph event dispatching
+ mxEvent.addListener(container, md,
+ mxUtils.bind(this, function(evt)
+ {
+ // Workaround for touch-based device not transferring
+ // the focus while editing with virtual keyboard
+ if (mxClient.IS_TOUCH && graph.isEditing())
+ {
+ graph.stopEditing(!graph.isInvokesStopCellEditing());
+ }
+
+ // Condition to avoid scrollbar events starting a rubberband
+ // selection
+ if (this.isContainerEvent(evt) && ((!mxClient.IS_IE &&
+ !mxClient.IS_GC && !mxClient.IS_OP && !mxClient.IS_SF) ||
+ !this.isScrollEvent(evt)))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ mxEvent.addListener(container, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isContainerEvent(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ mxEvent.addListener(container, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isContainerEvent(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+
+ // Adds listener for double click handling on background
+ mxEvent.addListener(container, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ graph.dblClick(evt);
+ })
+ );
+
+ // Workaround for touch events which started on some DOM node
+ // on top of the container, in which case the cells under the
+ // mouse for the move and up events are not detected.
+ var getState = function(evt)
+ {
+ var state = null;
+
+ // Workaround for touch events which started on some DOM node
+ // on top of the container, in which case the cells under the
+ // mouse for the move and up events are not detected.
+ if (mxClient.IS_TOUCH)
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+
+ // Dispatches the drop event to the graph which
+ // consumes and executes the source function
+ var pt = mxUtils.convertPoint(container, x, y);
+ state = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return state;
+ };
+
+ // Adds basic listeners for graph event dispatching outside of the
+ // container and finishing the handling of a single gesture
+ // Implemented via graph event dispatch loop to avoid duplicate events
+ // in Firefox and Chrome
+ graph.addMouseListener(
+ {
+ mouseDown: function(sender, me)
+ {
+ graph.panningHandler.hideMenu();
+ },
+ mouseMove: function() { },
+ mouseUp: function() { }
+ });
+ mxEvent.addListener(document, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ // Hides the tooltip if mouse is outside container
+ if (graph.tooltipHandler != null &&
+ graph.tooltipHandler.isHideOnHover())
+ {
+ graph.tooltipHandler.hide();
+ }
+
+ if (this.captureDocumentGesture && graph.isMouseDown &&
+ !mxEvent.isConsumed(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ })
+ );
+ mxEvent.addListener(document, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.captureDocumentGesture)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ }
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the HTML display.
+ */
+mxGraphView.prototype.createHtml = function()
+{
+ var container = this.graph.container;
+
+ if (container != null)
+ {
+ this.canvas = this.createHtmlPane('100%', '100%');
+
+ // Uses minimal size for inner DIVs on Canvas. This is required
+ // for correct event processing in IE. If we have an overlapping
+ // DIV then the events on the cells are only fired for labels.
+ this.backgroundPane = this.createHtmlPane('1px', '1px');
+ this.drawPane = this.createHtmlPane('1px', '1px');
+ this.overlayPane = this.createHtmlPane('1px', '1px');
+
+ this.canvas.appendChild(this.backgroundPane);
+ this.canvas.appendChild(this.drawPane);
+ this.canvas.appendChild(this.overlayPane);
+
+ container.appendChild(this.canvas);
+
+ // Implements minWidth/minHeight in quirks mode
+ if (mxClient.IS_QUIRKS)
+ {
+ var onResize = mxUtils.bind(this, function(evt)
+ {
+ var bounds = this.getGraphBounds();
+ var width = bounds.x + bounds.width + this.graph.border;
+ var height = bounds.y + bounds.height + this.graph.border;
+
+ this.updateHtmlCanvasSize(width, height);
+ });
+
+ mxEvent.addListener(window, 'resize', onResize);
+ }
+ }
+};
+
+/**
+ * Function: updateHtmlCanvasSize
+ *
+ * Updates the size of the HTML canvas.
+ */
+mxGraphView.prototype.updateHtmlCanvasSize = function(width, height)
+{
+ if (this.graph.container != null)
+ {
+ var ow = this.graph.container.offsetWidth;
+ var oh = this.graph.container.offsetHeight;
+
+ if (ow < width)
+ {
+ this.canvas.style.width = width + 'px';
+ }
+ else
+ {
+ this.canvas.style.width = '100%';
+ }
+
+ if (oh < height)
+ {
+ this.canvas.style.height = height + 'px';
+ }
+ else
+ {
+ this.canvas.style.height = '100%';
+ }
+ }
+};
+
+/**
+ * Function: createHtmlPane
+ *
+ * Creates and returns a drawing pane in HTML (DIV).
+ */
+mxGraphView.prototype.createHtmlPane = function(width, height)
+{
+ var pane = document.createElement('DIV');
+
+ if (width != null && height != null)
+ {
+ pane.style.position = 'absolute';
+ pane.style.left = '0px';
+ pane.style.top = '0px';
+
+ pane.style.width = width;
+ pane.style.height = height;
+ }
+ else
+ {
+ pane.style.position = 'relative';
+ }
+
+ return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the VML display.
+ */
+mxGraphView.prototype.createVml = function()
+{
+ var container = this.graph.container;
+
+ if (container != null)
+ {
+ var width = container.offsetWidth;
+ var height = container.offsetHeight;
+ this.canvas = this.createVmlPane(width, height);
+
+ this.backgroundPane = this.createVmlPane(width, height);
+ this.drawPane = this.createVmlPane(width, height);
+ this.overlayPane = this.createVmlPane(width, height);
+
+ this.canvas.appendChild(this.backgroundPane);
+ this.canvas.appendChild(this.drawPane);
+ this.canvas.appendChild(this.overlayPane);
+
+ container.appendChild(this.canvas);
+ }
+};
+
+/**
+ * Function: createVmlPane
+ *
+ * Creates a drawing pane in VML (group).
+ */
+mxGraphView.prototype.createVmlPane = function(width, height)
+{
+ var pane = document.createElement('v:group');
+
+ // At this point the width and height are potentially
+ // uninitialized. That's OK.
+ pane.style.position = 'absolute';
+ pane.style.left = '0px';
+ pane.style.top = '0px';
+
+ pane.style.width = width+'px';
+ pane.style.height = height+'px';
+
+ pane.setAttribute('coordsize', width+','+height);
+ pane.setAttribute('coordorigin', '0,0');
+
+ return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM nodes for the SVG display.
+ */
+mxGraphView.prototype.createSvg = function()
+{
+ var container = this.graph.container;
+ this.canvas = document.createElementNS(mxConstants.NS_SVG, 'g');
+
+ // For background image
+ this.backgroundPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.canvas.appendChild(this.backgroundPane);
+
+ // Adds two layers (background is early feature)
+ this.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.canvas.appendChild(this.drawPane);
+
+ this.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.canvas.appendChild(this.overlayPane);
+
+ var root = document.createElementNS(mxConstants.NS_SVG, 'svg');
+ root.style.width = '100%';
+ root.style.height = '100%';
+
+ if (mxClient.IS_IE)
+ {
+ root.style.marginBottom = '-4px';
+ }
+
+ root.appendChild(this.canvas);
+
+ if (container != null)
+ {
+ container.appendChild(root);
+
+ // Workaround for offset of container
+ var style = mxUtils.getCurrentStyle(container);
+
+ if (style.position == 'static')
+ {
+ container.style.position = 'relative';
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the view and all its resources.
+ */
+mxGraphView.prototype.destroy = function()
+{
+ var root = (this.canvas != null) ? this.canvas.ownerSVGElement : null;
+
+ if (root == null)
+ {
+ root = this.canvas;
+ }
+
+ if (root != null && root.parentNode != null)
+ {
+ this.clear(this.currentRoot, true);
+ mxEvent.removeAllListeners(document);
+ mxEvent.release(this.graph.container);
+ root.parentNode.removeChild(root);
+
+ this.canvas = null;
+ this.backgroundPane = null;
+ this.drawPane = null;
+ this.overlayPane = null;
+ }
+};
+
+/**
+ * Class: mxCurrentRootChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxCurrentRootChange(view, root)
+{
+ this.view = view;
+ this.root = root;
+ this.previous = root;
+ this.isUp = root == null;
+
+ if (!this.isUp)
+ {
+ var tmp = this.view.currentRoot;
+ var model = this.view.graph.getModel();
+
+ while (tmp != null)
+ {
+ if (tmp == root)
+ {
+ this.isUp = true;
+ break;
+ }
+
+ tmp = model.getParent(tmp);
+ }
+ }
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxCurrentRootChange.prototype.execute = function()
+{
+ var tmp = this.view.currentRoot;
+ this.view.currentRoot = this.previous;
+ this.previous = tmp;
+
+ var translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);
+
+ if (translate != null)
+ {
+ this.view.translate = new mxPoint(-translate.x, -translate.y);
+ }
+
+ var name = (this.isUp) ? mxEvent.UP : mxEvent.DOWN;
+ this.view.fireEvent(new mxEventObject(name,
+ 'root', this.view.currentRoot, 'previous', this.previous));
+
+ if (this.isUp)
+ {
+ this.view.clear(this.view.currentRoot, true);
+ this.view.validate();
+ }
+ else
+ {
+ this.view.refresh();
+ }
+
+ this.isUp = !this.isUp;
+};
diff --git a/src/js/view/mxLayoutManager.js b/src/js/view/mxLayoutManager.js
new file mode 100644
index 0000000..ee8ec65
--- /dev/null
+++ b/src/js/view/mxLayoutManager.js
@@ -0,0 +1,375 @@
+/**
+ * $Id: mxLayoutManager.js,v 1.21 2012-01-04 10:01:16 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxLayoutManager
+ *
+ * Implements a layout manager that updates the layout for a given transaction.
+ *
+ * Example:
+ *
+ * (code)
+ * var layoutMgr = new mxLayoutManager(graph);
+ * layoutMgr.getLayout = function(cell)
+ * {
+ * return layout;
+ * };
+ * (end)
+ *
+ * Event: mxEvent.LAYOUT_CELLS
+ *
+ * Fires between begin- and endUpdate after all cells have been layouted in
+ * <layoutCells>. The <code>cells</code> property contains all cells that have
+ * been passed to <layoutCells>.
+ *
+ * Constructor: mxLayoutManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxLayoutManager(graph)
+{
+ // Executes the layout before the changes are dispatched
+ this.undoHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.beforeUndo(evt.getProperty('edit'));
+ }
+ });
+
+ // Notifies the layout of a move operation inside a parent
+ this.moveHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.cellsMoved(evt.getProperty('cells'), evt.getProperty('event'));
+ }
+ });
+
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxLayoutManager.prototype = new mxEventSource();
+mxLayoutManager.prototype.constructor = mxLayoutManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxLayoutManager.prototype.graph = null;
+
+/**
+ * Variable: bubbling
+ *
+ * Specifies if the layout should bubble along
+ * the cell hierarchy. Default is true.
+ */
+mxLayoutManager.prototype.bubbling = true;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxLayoutManager.prototype.enabled = true;
+
+/**
+ * Variable: updateHandler
+ *
+ * Holds the function that handles the endUpdate event.
+ */
+mxLayoutManager.prototype.updateHandler = null;
+
+/**
+ * Variable: moveHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxLayoutManager.prototype.moveHandler = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxLayoutManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxLayoutManager.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isBubbling
+ *
+ * Returns true if a layout should bubble, that is, if the parent layout
+ * should be executed whenever a cell layout (layout of the children of
+ * a cell) has been executed. This implementation returns <bubbling>.
+ */
+mxLayoutManager.prototype.isBubbling = function()
+{
+ return this.bubbling;
+};
+
+/**
+ * Function: setBubbling
+ *
+ * Sets <bubbling>.
+ */
+mxLayoutManager.prototype.setBubbling = function(value)
+{
+ this.bubbling = value;
+};
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this layout operates on.
+ */
+mxLayoutManager.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxLayoutManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ var model = this.graph.getModel();
+ model.removeListener(this.undoHandler);
+ this.graph.removeListener(this.moveHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ var model = this.graph.getModel();
+ model.addListener(mxEvent.BEFORE_UNDO, this.undoHandler);
+ this.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler);
+ }
+};
+
+/**
+ * Function: getLayout
+ *
+ * Returns the layout to be executed for the given graph and parent.
+ */
+mxLayoutManager.prototype.getLayout = function(parent)
+{
+ return null;
+};
+
+/**
+ * Function: beforeUndo
+ *
+ * Called from the undoHandler.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.beforeUndo = function(undoableEdit)
+{
+ var cells = this.getCellsForChanges(undoableEdit.changes);
+ var model = this.getGraph().getModel();
+
+ // Adds all parent ancestors
+ if (this.isBubbling())
+ {
+ var tmp = model.getParents(cells);
+
+ while (tmp.length > 0)
+ {
+ cells = cells.concat(tmp);
+ tmp = model.getParents(tmp);
+ }
+ }
+
+ this.layoutCells(mxUtils.sortCells(cells, false));
+};
+
+/**
+ * Function: cellsMoved
+ *
+ * Called from the moveHandler.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.cellsMoved = function(cells, evt)
+{
+ if (cells != null &&
+ evt != null)
+ {
+ var point = mxUtils.convertPoint(this.getGraph().container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ var model = this.getGraph().getModel();
+
+ // Checks if a layout exists to take care of the moving
+ for (var i = 0; i < cells.length; i++)
+ {
+ var layout = this.getLayout(model.getParent(cells[i]));
+
+ if (layout != null)
+ {
+ layout.moveCell(cells[i], point.x, point.y);
+ }
+ }
+ }
+};
+
+/**
+ * Function: getCellsForEdit
+ *
+ * Returns the cells to be layouted for the given sequence of changes.
+ */
+mxLayoutManager.prototype.getCellsForChanges = function(changes)
+{
+ var result = [];
+ var hash = new Object();
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ if (change instanceof mxRootChange)
+ {
+ return [];
+ }
+ else
+ {
+ var cells = this.getCellsForChange(change);
+
+ for (var j = 0; j < cells.length; j++)
+ {
+ if (cells[j] != null)
+ {
+ var id = mxCellPath.create(cells[j]);
+
+ if (hash[id] == null)
+ {
+ hash[id] = cells[j];
+ result.push(cells[j]);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getCellsForChange
+ *
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.getCellsForChange = function(change)
+{
+ var model = this.getGraph().getModel();
+
+ if (change instanceof mxChildChange)
+ {
+ return [change.child, change.previous, model.getParent(change.child)];
+ }
+ else if (change instanceof mxTerminalChange ||
+ change instanceof mxGeometryChange)
+ {
+ return [change.cell, model.getParent(change.cell)];
+ }
+
+ return [];
+};
+
+/**
+ * Function: layoutCells
+ *
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.layoutCells = function(cells)
+{
+ if (cells.length > 0)
+ {
+ // Invokes the layouts while removing duplicates
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ var last = null;
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != model.getRoot() &&
+ cells[i] != last)
+ {
+ last = cells[i];
+ this.executeLayout(this.getLayout(last), last);
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells));
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: executeLayout
+ *
+ * Executes the given layout on the given parent.
+ */
+mxLayoutManager.prototype.executeLayout = function(layout, parent)
+{
+ if (layout != null && parent != null)
+ {
+ layout.execute(parent);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxLayoutManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/view/mxMultiplicity.js b/src/js/view/mxMultiplicity.js
new file mode 100644
index 0000000..c927d3f
--- /dev/null
+++ b/src/js/view/mxMultiplicity.js
@@ -0,0 +1,257 @@
+/**
+ * $Id: mxMultiplicity.js,v 1.24 2010-11-03 14:52:40 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxMultiplicity
+ *
+ * Defines invalid connections along with the error messages that they produce.
+ * To add or remove rules on a graph, you must add/remove instances of this
+ * class to <mxGraph.multiplicities>.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ * true, 'rectangle', null, null, 0, 2, ['circle'],
+ * 'Only 2 targets allowed',
+ * 'Only circle targets allowed'));
+ * (end)
+ *
+ * Defines a rule where each rectangle must be connected to no more than 2
+ * circles and no other types of targets are allowed.
+ *
+ * Constructor: mxMultiplicity
+ *
+ * Instantiate class mxMultiplicity in order to describe allowed
+ * connections in a graph. Not all constraints can be enforced while
+ * editing, some must be checked at validation time. The <countError> and
+ * <typeError> are treated as resource keys in <mxResources>.
+ *
+ * Parameters:
+ *
+ * source - Boolean indicating if this rule applies to the source or target
+ * terminal.
+ * type - Type of the source or target terminal that this rule applies to.
+ * See <type> for more information.
+ * attr - Optional attribute name to match the source or target terminal.
+ * value - Optional attribute value to match the source or target terminal.
+ * min - Minimum number of edges for this rule. Default is 1.
+ * max - Maximum number of edges for this rule. n means infinite. Default
+ * is n.
+ * validNeighbors - Array of types of the opposite terminal for which this
+ * rule applies.
+ * countError - Error to be displayed for invalid number of edges.
+ * typeError - Error to be displayed for invalid opposite terminals.
+ * validNeighborsAllowed - Optional boolean indicating if the array of
+ * opposite types should be valid or invalid.
+ */
+function mxMultiplicity(source, type, attr, value, min, max,
+ validNeighbors, countError, typeError, validNeighborsAllowed)
+{
+ this.source = source;
+ this.type = type;
+ this.attr = attr;
+ this.value = value;
+ this.min = (min != null) ? min : 0;
+ this.max = (max != null) ? max : 'n';
+ this.validNeighbors = validNeighbors;
+ this.countError = mxResources.get(countError) || countError;
+ this.typeError = mxResources.get(typeError) || typeError;
+ this.validNeighborsAllowed = (validNeighborsAllowed != null) ?
+ validNeighborsAllowed : true;
+};
+
+/**
+ * Variable: type
+ *
+ * Defines the type of the source or target terminal. The type is a string
+ * passed to <mxUtils.isNode> together with the source or target vertex
+ * value as the first argument.
+ */
+mxMultiplicity.prototype.type = null;
+
+/**
+ * Variable: attr
+ *
+ * Optional string that specifies the attributename to be passed to
+ * <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.attr = null;
+
+/**
+ * Variable: value
+ *
+ * Optional string that specifies the value of the attribute to be passed
+ * to <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.value = null;
+
+/**
+ * Variable: source
+ *
+ * Boolean that specifies if the rule is applied to the source or target
+ * terminal of an edge.
+ */
+mxMultiplicity.prototype.source = null;
+
+/**
+ * Variable: min
+ *
+ * Defines the minimum number of connections for which this rule applies.
+ * Default is 0.
+ */
+mxMultiplicity.prototype.min = null;
+
+/**
+ * Variable: max
+ *
+ * Defines the maximum number of connections for which this rule applies.
+ * A value of 'n' means unlimited times. Default is 'n'.
+ */
+mxMultiplicity.prototype.max = null;
+
+/**
+ * Variable: validNeighbors
+ *
+ * Holds an array of strings that specify the type of neighbor for which
+ * this rule applies. The strings are used in <mxCell.is> on the opposite
+ * terminal to check if the rule applies to the connection.
+ */
+mxMultiplicity.prototype.validNeighbors = null;
+
+/**
+ * Variable: validNeighborsAllowed
+ *
+ * Boolean indicating if the list of validNeighbors are those that are allowed
+ * for this rule or those that are not allowed for this rule.
+ */
+mxMultiplicity.prototype.validNeighborsAllowed = true;
+
+/**
+ * Variable: countError
+ *
+ * Holds the localized error message to be displayed if the number of
+ * connections for which the rule applies is smaller than <min> or greater
+ * than <max>.
+ */
+mxMultiplicity.prototype.countError = null;
+
+/**
+ * Variable: typeError
+ *
+ * Holds the localized error message to be displayed if the type of the
+ * neighbor for a connection does not match the rule.
+ */
+mxMultiplicity.prototype.typeError = null;
+
+/**
+ * Function: check
+ *
+ * Checks the multiplicity for the given arguments and returns the error
+ * for the given connection or null if the multiplicity does not apply.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph> instance.
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * sourceOut - Number of outgoing edges from the source terminal.
+ * targetIn - Number of incoming edges for the target terminal.
+ */
+mxMultiplicity.prototype.check = function(graph, edge, source, target, sourceOut, targetIn)
+{
+ var error = '';
+
+ if ((this.source && this.checkTerminal(graph, source, edge)) ||
+ (!this.source && this.checkTerminal(graph, target, edge)))
+ {
+ if (this.countError != null &&
+ ((this.source && (this.max == 0 || (sourceOut >= this.max))) ||
+ (!this.source && (this.max == 0 || (targetIn >= this.max)))))
+ {
+ error += this.countError + '\n';
+ }
+
+ if (this.validNeighbors != null && this.typeError != null && this.validNeighbors.length > 0)
+ {
+ var isValid = this.checkNeighbors(graph, edge, source, target);
+
+ if (!isValid)
+ {
+ error += this.typeError + '\n';
+ }
+ }
+ }
+
+ return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: checkNeighbors
+ *
+ * Checks if there are any valid neighbours in <validNeighbors>. This is only
+ * called if <validNeighbors> is a non-empty array.
+ */
+mxMultiplicity.prototype.checkNeighbors = function(graph, edge, source, target)
+{
+ var sourceValue = graph.model.getValue(source);
+ var targetValue = graph.model.getValue(target);
+ var isValid = !this.validNeighborsAllowed;
+ var valid = this.validNeighbors;
+
+ for (var j = 0; j < valid.length; j++)
+ {
+ if (this.source &&
+ this.checkType(graph, targetValue, valid[j]))
+ {
+ isValid = this.validNeighborsAllowed;
+ break;
+ }
+ else if (!this.source &&
+ this.checkType(graph, sourceValue, valid[j]))
+ {
+ isValid = this.validNeighborsAllowed;
+ break;
+ }
+ }
+
+ return isValid;
+};
+
+/**
+ * Function: checkTerminal
+ *
+ * Checks the given terminal cell and returns true if this rule applies. The
+ * given cell is the source or target of the given edge, depending on
+ * <source>. This implementation uses <checkType> on the terminal's value.
+ */
+mxMultiplicity.prototype.checkTerminal = function(graph, terminal, edge)
+{
+ var value = graph.model.getValue(terminal);
+
+ return this.checkType(graph, value, this.type, this.attr, this.value);
+};
+
+/**
+ * Function: checkType
+ *
+ * Checks the type of the given value.
+ */
+mxMultiplicity.prototype.checkType = function(graph, value, type, attr, attrValue)
+{
+ if (value != null)
+ {
+ if (!isNaN(value.nodeType)) // Checks if value is a DOM node
+ {
+ return mxUtils.isNode(value, type, attr, attrValue);
+ }
+ else
+ {
+ return value == type;
+ }
+ }
+
+ return false;
+};
diff --git a/src/js/view/mxOutline.js b/src/js/view/mxOutline.js
new file mode 100644
index 0000000..a0d6fd3
--- /dev/null
+++ b/src/js/view/mxOutline.js
@@ -0,0 +1,649 @@
+/**
+ * $Id: mxOutline.js,v 1.81 2012-06-20 14:13:37 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxOutline
+ *
+ * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
+ * to enable updates while the source graph is panning.
+ *
+ * Example:
+ *
+ * (code)
+ * var outline = new mxOutline(graph, div);
+ * (end)
+ *
+ * If the selection border in the outline appears behind the contents of the
+ * graph, then you can use the following code. (This may happen when using a
+ * transparent container for the outline in IE.)
+ *
+ * (code)
+ * mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_EXACT;
+ * (end)
+ *
+ * To move the graph to the top, left corner the following code can be used.
+ *
+ * (code)
+ * var scale = graph.view.scale;
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
+ * (end)
+ *
+ * To toggle the suspended mode, the following can be used.
+ *
+ * (code)
+ * outline.suspended = !outln.suspended;
+ * if (!outline.suspended)
+ * {
+ * outline.update(true);
+ * }
+ * (end)
+ *
+ * Constructor: mxOutline
+ *
+ * Constructs a new outline for the specified graph inside the given
+ * container.
+ *
+ * Parameters:
+ *
+ * source - <mxGraph> to create the outline for.
+ * container - DOM node that will contain the outline.
+ */
+function mxOutline(source, container)
+{
+ this.source = source;
+
+ if (container != null)
+ {
+ this.init(container);
+ }
+};
+
+/**
+ * Function: source
+ *
+ * Reference to the source <mxGraph>.
+ */
+mxOutline.prototype.source = null;
+
+/**
+ * Function: outline
+ *
+ * Reference to the outline <mxGraph>.
+ */
+mxOutline.prototype.outline = null;
+
+/**
+ * Function: graphRenderHint
+ *
+ * Renderhint to be used for the outline graph. Default is faster.
+ */
+mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxOutline.prototype.enabled = true;
+
+/**
+ * Variable: showViewport
+ *
+ * Specifies a viewport rectangle should be shown. Default is true.
+ */
+mxOutline.prototype.showViewport = true;
+
+/**
+ * Variable: border
+ *
+ * Border to be added at the bottom and right. Default is 10.
+ */
+mxOutline.prototype.border = 10;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies the size of the sizer handler. Default is 8.
+ */
+mxOutline.prototype.sizerSize = 8;
+
+/**
+ * Variable: updateOnPan
+ *
+ * Specifies if <update> should be called for <mxEvent.PAN> in the source
+ * graph. Default is false.
+ */
+mxOutline.prototype.updateOnPan = false;
+
+/**
+ * Variable: sizerImage
+ *
+ * Optional <mxImage> to be used for the sizer. Default is null.
+ */
+mxOutline.prototype.sizerImage = null;
+
+/**
+ * Variable: suspended
+ *
+ * Optional boolean flag to suspend updates. Default is false. This flag will
+ * also suspend repaints of the outline. To toggle this switch, use the
+ * following code.
+ *
+ * (code)
+ * nav.suspended = !nav.suspended;
+ *
+ * if (!nav.suspended)
+ * {
+ * nav.update(true);
+ * }
+ * (end)
+ */
+mxOutline.prototype.suspended = false;
+
+/**
+ * Function: init
+ *
+ * Initializes the outline inside the given container.
+ */
+mxOutline.prototype.init = function(container)
+{
+ this.outline = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());
+ this.outline.foldingEnabled = false;
+ this.outline.autoScroll = false;
+
+ // Do not repaint when suspended
+ var outlineGraphModelChanged = this.outline.graphModelChanged;
+ this.outline.graphModelChanged = mxUtils.bind(this, function(changes)
+ {
+ if (!this.suspended && this.outline != null)
+ {
+ outlineGraphModelChanged.apply(this.outline, arguments);
+ }
+ });
+
+ // Enables faster painting in SVG
+ if (mxClient.IS_SVG)
+ {
+ var node = this.outline.getView().getCanvas().parentNode;
+ node.setAttribute('shape-rendering', 'optimizeSpeed');
+ node.setAttribute('image-rendering', 'optimizeSpeed');
+ }
+
+ // Hides cursors and labels
+ this.outline.labelsVisible = false;
+ this.outline.setEnabled(false);
+
+ this.updateHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (!this.suspended && !this.active)
+ {
+ this.update();
+ }
+ });
+
+ // Updates the scale of the outline after a change of the main graph
+ this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
+ this.outline.addMouseListener(this);
+
+ // Adds listeners to keep the outline in sync with the source graph
+ var view = this.source.getView();
+ view.addListener(mxEvent.SCALE, this.updateHandler);
+ view.addListener(mxEvent.TRANSLATE, this.updateHandler);
+ view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
+ view.addListener(mxEvent.DOWN, this.updateHandler);
+ view.addListener(mxEvent.UP, this.updateHandler);
+
+ // Updates blue rectangle on scroll
+ mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+
+ this.panHandler = mxUtils.bind(this, function(sender)
+ {
+ if (this.updateOnPan)
+ {
+ this.updateHandler.apply(this, arguments);
+ }
+ });
+ this.source.addListener(mxEvent.PAN, this.panHandler);
+
+ // Refreshes the graph in the outline after a refresh of the main graph
+ this.refreshHandler = mxUtils.bind(this, function(sender)
+ {
+ this.outline.setStylesheet(this.source.getStylesheet());
+ this.outline.refresh();
+ });
+ this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
+
+ // Creates the blue rectangle for the viewport
+ this.bounds = new mxRectangle(0, 0, 0, 0);
+ this.selectionBorder = new mxRectangleShape(this.bounds, null,
+ mxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH);
+ this.selectionBorder.dialect =
+ (this.outline.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.selectionBorder.crisp = true;
+ this.selectionBorder.init(this.outline.getView().getOverlayPane());
+ mxEvent.redirectMouseEvents(this.selectionBorder.node, this.outline);
+ this.selectionBorder.node.style.background = '';
+
+ // Creates a small blue rectangle for sizing (sizer handle)
+ this.sizer = this.createSizer();
+ this.sizer.init(this.outline.getView().getOverlayPane());
+
+ if (this.enabled)
+ {
+ this.sizer.node.style.cursor = 'pointer';
+ }
+
+ // Redirects all events from the sizerhandle to the outline
+ mxEvent.addListener(this.sizer.node, (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown',
+ mxUtils.bind(this, function(evt)
+ {
+ this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+ })
+ );
+
+ this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+ this.sizer.node.style.display = this.selectionBorder.node.style.display;
+ this.selectionBorder.node.style.cursor = 'move';
+
+ this.update(false);
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxOutline.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: setZoomEnabled
+ *
+ * Enables or disables the zoom handling by showing or hiding the respective
+ * handle.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setZoomEnabled = function(value)
+{
+ this.sizer.node.style.visibility = (value) ? 'visible' : 'hidden';
+};
+
+/**
+ * Function: refresh
+ *
+ * Invokes <update> and revalidate the outline. This method is deprecated.
+ */
+mxOutline.prototype.refresh = function()
+{
+ this.update(true);
+};
+
+/**
+ * Function: createSizer
+ *
+ * Creates the shape used as the sizer.
+ */
+mxOutline.prototype.createSizer = function()
+{
+ if (this.sizerImage != null)
+ {
+ var sizer = new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src);
+ sizer.dialect = this.outline.dialect;
+
+ return sizer;
+ }
+ else
+ {
+ var sizer = new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize, this.sizerSize),
+ mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR);
+ sizer.dialect = this.outline.dialect;
+ sizer.crisp = true;
+
+ return sizer;
+ }
+};
+
+/**
+ * Function: getSourceContainerSize
+ *
+ * Returns the size of the source container.
+ */
+mxOutline.prototype.getSourceContainerSize = function()
+{
+ return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight);
+};
+
+/**
+ * Function: getOutlineOffset
+ *
+ * Returns the offset for drawing the outline graph.
+ */
+mxOutline.prototype.getOutlineOffset = function(scale)
+{
+ return null;
+};
+
+/**
+ * Function: update
+ *
+ * Updates the outline.
+ */
+mxOutline.prototype.update = function(revalidate)
+{
+ if (this.source != null)
+ {
+ var sourceScale = this.source.view.scale;
+ var scaledGraphBounds = this.source.getGraphBounds();
+ var unscaledGraphBounds = new mxRectangle(scaledGraphBounds.x / sourceScale + this.source.panDx,
+ scaledGraphBounds.y / sourceScale + this.source.panDy, scaledGraphBounds.width / sourceScale,
+ scaledGraphBounds.height / sourceScale);
+
+ var unscaledFinderBounds = new mxRectangle(0, 0,
+ this.source.container.clientWidth / sourceScale,
+ this.source.container.clientHeight / sourceScale);
+
+ var union = unscaledGraphBounds.clone();
+ union.add(unscaledFinderBounds);
+
+ // Zooms to the scrollable area if that is bigger than the graph
+ var size = this.getSourceContainerSize();
+ var completeWidth = Math.max(size.width / sourceScale, union.width);
+ var completeHeight = Math.max(size.height / sourceScale, union.height);
+
+ var availableWidth = Math.max(0, this.outline.container.clientWidth - this.border);
+ var availableHeight = Math.max(0, this.outline.container.clientHeight - this.border);
+
+ var outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight);
+ var scale = outlineScale;
+
+ if (scale > 0)
+ {
+ if (this.outline.getView().scale != scale)
+ {
+ this.outline.getView().scale = scale;
+ revalidate = true;
+ }
+
+ var navView = this.outline.getView();
+
+ if (navView.currentRoot != this.source.getView().currentRoot)
+ {
+ navView.setCurrentRoot(this.source.getView().currentRoot);
+ }
+
+ var t = this.source.view.translate;
+ var tx = t.x + this.source.panDx;
+ var ty = t.y + this.source.panDy;
+
+ var off = this.getOutlineOffset(scale);
+
+ if (off != null)
+ {
+ tx += off.x;
+ ty += off.y;
+ }
+
+ if (unscaledGraphBounds.x < 0)
+ {
+ tx = tx - unscaledGraphBounds.x;
+ }
+ if (unscaledGraphBounds.y < 0)
+ {
+ ty = ty - unscaledGraphBounds.y;
+ }
+
+ if (navView.translate.x != tx || navView.translate.y != ty)
+ {
+ navView.translate.x = tx;
+ navView.translate.y = ty;
+ revalidate = true;
+ }
+
+ // Prepares local variables for computations
+ var t2 = navView.translate;
+ scale = this.source.getView().scale;
+ var scale2 = scale / navView.scale;
+ var scale3 = 1.0 / navView.scale;
+ var container = this.source.container;
+
+ // Updates the bounds of the viewrect in the navigation
+ this.bounds = new mxRectangle(
+ (t2.x - t.x - this.source.panDx) / scale3,
+ (t2.y - t.y - this.source.panDy) / scale3,
+ (container.clientWidth / scale2),
+ (container.clientHeight / scale2));
+
+ // Adds the scrollbar offset to the finder
+ this.bounds.x += this.source.container.scrollLeft * navView.scale / scale;
+ this.bounds.y += this.source.container.scrollTop * navView.scale / scale;
+
+ var b = this.selectionBorder.bounds;
+
+ if (b.x != this.bounds.x || b.y != this.bounds.y || b.width != this.bounds.width || b.height != this.bounds.height)
+ {
+ this.selectionBorder.bounds = this.bounds;
+ this.selectionBorder.redraw();
+ }
+
+ // Updates the bounds of the zoom handle at the bottom right
+ var b = this.sizer.bounds;
+ var b2 = new mxRectangle(this.bounds.x + this.bounds.width - b.width / 2,
+ this.bounds.y + this.bounds.height - b.height / 2, b.width, b.height);
+
+ if (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height)
+ {
+ this.sizer.bounds = b2;
+
+ // Avoids update of visibility in redraw for VML
+ if (this.sizer.node.style.visibility != 'hidden')
+ {
+ this.sizer.redraw();
+ }
+ }
+
+ if (revalidate)
+ {
+ this.outline.view.revalidate();
+ }
+ }
+ }
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by starting a translation or zoom.
+ */
+mxOutline.prototype.mouseDown = function(sender, me)
+{
+ if (this.enabled && this.showViewport)
+ {
+ this.zoom = me.isSource(this.sizer);
+ this.startX = me.getX();
+ this.startY = me.getY();
+ this.active = true;
+
+ if (this.source.useScrollbarsForPanning &&
+ mxUtils.hasScrollbars(this.source.container))
+ {
+ this.dx0 = this.source.container.scrollLeft;
+ this.dy0 = this.source.container.scrollTop;
+ }
+ else
+ {
+ this.dx0 = 0;
+ this.dy0 = 0;
+ }
+ }
+
+ me.consume();
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by previewing the viewrect in <graph> and updating the
+ * rectangle that represents the viewrect in the outline.
+ */
+mxOutline.prototype.mouseMove = function(sender, me)
+{
+ if (this.active)
+ {
+ this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+ this.sizer.node.style.display = this.selectionBorder.node.style.display;
+
+ var dx = me.getX() - this.startX;
+ var dy = me.getY() - this.startY;
+ var bounds = null;
+
+ if (!this.zoom)
+ {
+ // Previews the panning on the source graph
+ var scale = this.outline.getView().scale;
+ bounds = new mxRectangle(this.bounds.x + dx,
+ this.bounds.y + dy, this.bounds.width, this.bounds.height);
+ this.selectionBorder.bounds = bounds;
+ this.selectionBorder.redraw();
+ dx /= scale;
+ dx *= this.source.getView().scale;
+ dy /= scale;
+ dy *= this.source.getView().scale;
+ this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
+ }
+ else
+ {
+ // Does *not* preview zooming on the source graph
+ var container = this.source.container;
+ var viewRatio = container.clientWidth / container.clientHeight;
+ dy = dx / viewRatio;
+ bounds = new mxRectangle(this.bounds.x,
+ this.bounds.y,
+ Math.max(1, this.bounds.width + dx),
+ Math.max(1, this.bounds.height + dy));
+ this.selectionBorder.bounds = bounds;
+ this.selectionBorder.redraw();
+ }
+
+ // Updates the zoom handle
+ var b = this.sizer.bounds;
+ this.sizer.bounds = new mxRectangle(
+ bounds.x + bounds.width - b.width / 2,
+ bounds.y + bounds.height - b.height / 2,
+ b.width, b.height);
+
+ // Avoids update of visibility in redraw for VML
+ if (this.sizer.node.style.visibility != 'hidden')
+ {
+ this.sizer.redraw();
+ }
+
+ me.consume();
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by applying the translation or zoom to <graph>.
+ */
+mxOutline.prototype.mouseUp = function(sender, me)
+{
+ if (this.active)
+ {
+ var dx = me.getX() - this.startX;
+ var dy = me.getY() - this.startY;
+
+ if (Math.abs(dx) > 0 || Math.abs(dy) > 0)
+ {
+ if (!this.zoom)
+ {
+ // Applies the new translation if the source
+ // has no scrollbars
+ if (!this.source.useScrollbarsForPanning ||
+ !mxUtils.hasScrollbars(this.source.container))
+ {
+ this.source.panGraph(0, 0);
+ dx /= this.outline.getView().scale;
+ dy /= this.outline.getView().scale;
+ var t = this.source.getView().translate;
+ this.source.getView().setTranslate(t.x - dx, t.y - dy);
+ }
+ }
+ else
+ {
+ // Applies the new zoom
+ var w = this.selectionBorder.bounds.width;
+ var scale = this.source.getView().scale;
+ this.source.zoomTo(scale - (dx * scale) / w, false);
+ }
+
+ this.update();
+ me.consume();
+ }
+
+ // Resets the state of the handler
+ this.index = null;
+ this.active = false;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroy this outline and removes all listeners from <source>.
+ */
+mxOutline.prototype.destroy = function()
+{
+ if (this.source != null)
+ {
+ this.source.removeListener(this.panHandler);
+ this.source.removeListener(this.refreshHandler);
+ this.source.getModel().removeListener(this.updateHandler);
+ this.source.getView().removeListener(this.updateHandler);
+ mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+ this.source = null;
+ }
+
+ if (this.outline != null)
+ {
+ this.outline.removeMouseListener(this);
+ this.outline.destroy();
+ this.outline = null;
+ }
+
+ if (this.selectionBorder != null)
+ {
+ this.selectionBorder.destroy();
+ this.selectionBorder = null;
+ }
+
+ if (this.sizer != null)
+ {
+ this.sizer.destroy();
+ this.sizer = null;
+ }
+};
diff --git a/src/js/view/mxPerimeter.js b/src/js/view/mxPerimeter.js
new file mode 100644
index 0000000..7aaa187
--- /dev/null
+++ b/src/js/view/mxPerimeter.js
@@ -0,0 +1,484 @@
+/**
+ * $Id: mxPerimeter.js,v 1.28 2012-01-11 09:06:56 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxPerimeter =
+{
+ /**
+ * Class: mxPerimeter
+ *
+ * Provides various perimeter functions to be used in a style
+ * as the value of <mxConstants.STYLE_PERIMETER>. Perimeters for
+ * rectangle, circle, rhombus and triangle are available.
+ *
+ * Example:
+ *
+ * (code)
+ * <add as="perimeter">mxPerimeter.RightAngleRectanglePerimeter</add>
+ * (end)
+ *
+ * Or programmatically:
+ *
+ * (code)
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ * (end)
+ *
+ * When adding new perimeter functions, it is recommended to use the
+ * mxPerimeter-namespace as follows:
+ *
+ * (code)
+ * mxPerimeter.CustomPerimeter = function (bounds, vertex, next, orthogonal)
+ * {
+ * var x = 0; // Calculate x-coordinate
+ * var y = 0; // Calculate y-coordainte
+ *
+ * return new mxPoint(x, y);
+ * }
+ * (end)
+ *
+ * The new perimeter should then be registered in the <mxStyleRegistry> as follows:
+ * (code)
+ * mxStyleRegistry.putValue('customPerimeter', mxPerimeter.CustomPerimeter);
+ * (end)
+ *
+ * The custom perimeter above can now be used in a specific vertex as follows:
+ *
+ * (code)
+ * model.setStyle(vertex, 'perimeter=customPerimeter');
+ * (end)
+ *
+ * Note that the key of the <mxStyleRegistry> entry for the function should
+ * be used in string values, unless <mxGraphView.allowEval> is true, in
+ * which case you can also use mxPerimeter.CustomPerimeter for the value in
+ * the cell style above.
+ *
+ * Or it can be used for all vertices in the graph as follows:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.CustomPerimeter;
+ * (end)
+ *
+ * Note that the object can be used directly when programmatically setting
+ * the value, but the key in the <mxStyleRegistry> should be used when
+ * setting the value via a key, value pair in a cell style.
+ *
+ * The parameters are explained in <RectanglePerimeter>.
+ *
+ * Function: RectanglePerimeter
+ *
+ * Describes a rectangular perimeter for the given bounds.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that represents the absolute bounds of the
+ * vertex.
+ * vertex - <mxCellState> that represents the vertex.
+ * next - <mxPoint> that represents the nearest neighbour point on the
+ * given edge.
+ * orthogonal - Boolean that specifies if the orthogonal projection onto
+ * the perimeter should be returned. If this is false then the intersection
+ * of the perimeter and the line between the next and the center point is
+ * returned.
+ */
+ RectanglePerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var cx = bounds.getCenterX();
+ var cy = bounds.getCenterY();
+ var dx = next.x - cx;
+ var dy = next.y - cy;
+ var alpha = Math.atan2(dy, dx);
+ var p = new mxPoint(0, 0);
+ var pi = Math.PI;
+ var pi2 = Math.PI/2;
+ var beta = pi2 - alpha;
+ var t = Math.atan2(bounds.height, bounds.width);
+
+ if (alpha < -pi + t || alpha > pi - t)
+ {
+ // Left edge
+ p.x = bounds.x;
+ p.y = cy - bounds.width * Math.tan(alpha) / 2;
+ }
+ else if (alpha < -t)
+ {
+ // Top Edge
+ p.y = bounds.y;
+ p.x = cx - bounds.height * Math.tan(beta) / 2;
+ }
+ else if (alpha < t)
+ {
+ // Right Edge
+ p.x = bounds.x + bounds.width;
+ p.y = cy + bounds.width * Math.tan(alpha) / 2;
+ }
+ else
+ {
+ // Bottom Edge
+ p.y = bounds.y + bounds.height;
+ p.x = cx + bounds.height * Math.tan(beta) / 2;
+ }
+
+ if (orthogonal)
+ {
+ if (next.x >= bounds.x &&
+ next.x <= bounds.x + bounds.width)
+ {
+ p.x = next.x;
+ }
+ else if (next.y >= bounds.y &&
+ next.y <= bounds.y + bounds.height)
+ {
+ p.y = next.y;
+ }
+ if (next.x < bounds.x)
+ {
+ p.x = bounds.x;
+ }
+ else if (next.x > bounds.x + bounds.width)
+ {
+ p.x = bounds.x + bounds.width;
+ }
+ if (next.y < bounds.y)
+ {
+ p.y = bounds.y;
+ }
+ else if (next.y > bounds.y + bounds.height)
+ {
+ p.y = bounds.y + bounds.height;
+ }
+ }
+
+ return p;
+ },
+
+ /**
+ * Function: EllipsePerimeter
+ *
+ * Describes an elliptic perimeter. See <RectanglePerimeter>
+ * for a description of the parameters.
+ */
+ EllipsePerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var a = bounds.width / 2;
+ var b = bounds.height / 2;
+ var cx = x + a;
+ var cy = y + b;
+ var px = next.x;
+ var py = next.y;
+
+ // Calculates straight line equation through
+ // point and ellipse center y = d * x + h
+ var dx = parseInt(px - cx);
+ var dy = parseInt(py - cy);
+
+ if (dx == 0 && dy != 0)
+ {
+ return new mxPoint(cx, cy + b * dy / Math.abs(dy));
+ }
+ else if (dx == 0 && dy == 0)
+ {
+ return new mxPoint(px, py);
+ }
+
+ if (orthogonal)
+ {
+ if (py >= y && py <= y + bounds.height)
+ {
+ var ty = py - cy;
+ var tx = Math.sqrt(a*a*(1-(ty*ty)/(b*b))) || 0;
+
+ if (px <= x)
+ {
+ tx = -tx;
+ }
+
+ return new mxPoint(cx+tx, py);
+ }
+
+ if (px >= x && px <= x + bounds.width)
+ {
+ var tx = px - cx;
+ var ty = Math.sqrt(b*b*(1-(tx*tx)/(a*a))) || 0;
+
+ if (py <= y)
+ {
+ ty = -ty;
+ }
+
+ return new mxPoint(px, cy+ty);
+ }
+ }
+
+ // Calculates intersection
+ var d = dy / dx;
+ var h = cy - d * cx;
+ var e = a * a * d * d + b * b;
+ var f = -2 * cx * e;
+ var g = a * a * d * d * cx * cx +
+ b * b * cx * cx -
+ a * a * b * b;
+ var det = Math.sqrt(f * f - 4 * e * g);
+
+ // Two solutions (perimeter points)
+ var xout1 = (-f + det) / (2 * e);
+ var xout2 = (-f - det) / (2 * e);
+ var yout1 = d * xout1 + h;
+ var yout2 = d * xout2 + h;
+ var dist1 = Math.sqrt(Math.pow((xout1 - px), 2)
+ + Math.pow((yout1 - py), 2));
+ var dist2 = Math.sqrt(Math.pow((xout2 - px), 2)
+ + Math.pow((yout2 - py), 2));
+
+ // Correct solution
+ var xout = 0;
+ var yout = 0;
+
+ if (dist1 < dist2)
+ {
+ xout = xout1;
+ yout = yout1;
+ }
+ else
+ {
+ xout = xout2;
+ yout = yout2;
+ }
+
+ return new mxPoint(xout, yout);
+ },
+
+ /**
+ * Function: RhombusPerimeter
+ *
+ * Describes a rhombus (aka diamond) perimeter. See <RectanglePerimeter>
+ * for a description of the parameters.
+ */
+ RhombusPerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ var cx = x + w / 2;
+ var cy = y + h / 2;
+
+ var px = next.x;
+ var py = next.y;
+
+ // Special case for intersecting the diamond's corners
+ if (cx == px)
+ {
+ if (cy > py)
+ {
+ return new mxPoint(cx, y); // top
+ }
+ else
+ {
+ return new mxPoint(cx, y + h); // bottom
+ }
+ }
+ else if (cy == py)
+ {
+ if (cx > px)
+ {
+ return new mxPoint(x, cy); // left
+ }
+ else
+ {
+ return new mxPoint(x + w, cy); // right
+ }
+ }
+
+ var tx = cx;
+ var ty = cy;
+
+ if (orthogonal)
+ {
+ if (px >= x && px <= x + w)
+ {
+ tx = px;
+ }
+ else if (py >= y && py <= y + h)
+ {
+ ty = py;
+ }
+ }
+
+ // In which quadrant will the intersection be?
+ // set the slope and offset of the border line accordingly
+ if (px < cx)
+ {
+ if (py < cy)
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y, x, cy);
+ }
+ else
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y + h, x, cy);
+ }
+ }
+ else if (py < cy)
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y, x + w, cy);
+ }
+ else
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y + h, x + w, cy);
+ }
+ },
+
+ /**
+ * Function: TrianglePerimeter
+ *
+ * Describes a triangle perimeter. See <RectanglePerimeter>
+ * for a description of the parameters.
+ */
+ TrianglePerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var direction = (vertex != null) ?
+ vertex.style[mxConstants.STYLE_DIRECTION] : null;
+ var vertical = direction == mxConstants.DIRECTION_NORTH ||
+ direction == mxConstants.DIRECTION_SOUTH;
+
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ var cx = x + w / 2;
+ var cy = y + h / 2;
+
+ var start = new mxPoint(x, y);
+ var corner = new mxPoint(x + w, cy);
+ var end = new mxPoint(x, y + h);
+
+ if (direction == mxConstants.DIRECTION_NORTH)
+ {
+ start = end;
+ corner = new mxPoint(cx, y);
+ end = new mxPoint(x + w, y + h);
+ }
+ else if (direction == mxConstants.DIRECTION_SOUTH)
+ {
+ corner = new mxPoint(cx, y + h);
+ end = new mxPoint(x + w, y);
+ }
+ else if (direction == mxConstants.DIRECTION_WEST)
+ {
+ start = new mxPoint(x + w, y);
+ corner = new mxPoint(x, cy);
+ end = new mxPoint(x + w, y + h);
+ }
+
+ var dx = next.x - cx;
+ var dy = next.y - cy;
+
+ var alpha = (vertical) ? Math.atan2(dx, dy) : Math.atan2(dy, dx);
+ var t = (vertical) ? Math.atan2(w, h) : Math.atan2(h, w);
+
+ var base = false;
+
+ if (direction == mxConstants.DIRECTION_NORTH ||
+ direction == mxConstants.DIRECTION_WEST)
+ {
+ base = alpha > -t && alpha < t;
+ }
+ else
+ {
+ base = alpha < -Math.PI + t || alpha > Math.PI - t;
+ }
+
+ var result = null;
+
+ if (base)
+ {
+ if (orthogonal && ((vertical && next.x >= start.x && next.x <= end.x) ||
+ (!vertical && next.y >= start.y && next.y <= end.y)))
+ {
+ if (vertical)
+ {
+ result = new mxPoint(next.x, start.y);
+ }
+ else
+ {
+ result = new mxPoint(start.x, next.y);
+ }
+ }
+ else
+ {
+ if (direction == mxConstants.DIRECTION_NORTH)
+ {
+ result = new mxPoint(x + w / 2 + h * Math.tan(alpha) / 2,
+ y + h);
+ }
+ else if (direction == mxConstants.DIRECTION_SOUTH)
+ {
+ result = new mxPoint(x + w / 2 - h * Math.tan(alpha) / 2,
+ y);
+ }
+ else if (direction == mxConstants.DIRECTION_WEST)
+ {
+ result = new mxPoint(x + w, y + h / 2 +
+ w * Math.tan(alpha) / 2);
+ }
+ else
+ {
+ result = new mxPoint(x, y + h / 2 -
+ w * Math.tan(alpha) / 2);
+ }
+ }
+ }
+ else
+ {
+ if (orthogonal)
+ {
+ var pt = new mxPoint(cx, cy);
+
+ if (next.y >= y && next.y <= y + h)
+ {
+ pt.x = (vertical) ? cx : (
+ (direction == mxConstants.DIRECTION_WEST) ?
+ x + w : x);
+ pt.y = next.y;
+ }
+ else if (next.x >= x && next.x <= x + w)
+ {
+ pt.x = next.x;
+ pt.y = (!vertical) ? cy : (
+ (direction == mxConstants.DIRECTION_NORTH) ?
+ y + h : y);
+ }
+
+ // Compute angle
+ dx = next.x - pt.x;
+ dy = next.y - pt.y;
+
+ cx = pt.x;
+ cy = pt.y;
+ }
+
+ if ((vertical && next.x <= x + w / 2) ||
+ (!vertical && next.y <= y + h / 2))
+ {
+ result = mxUtils.intersection(next.x, next.y, cx, cy,
+ start.x, start.y, corner.x, corner.y);
+ }
+ else
+ {
+ result = mxUtils.intersection(next.x, next.y, cx, cy,
+ corner.x, corner.y, end.x, end.y);
+ }
+ }
+
+ if (result == null)
+ {
+ result = new mxPoint(cx, cy);
+ }
+
+ return result;
+ }
+};
diff --git a/src/js/view/mxPrintPreview.js b/src/js/view/mxPrintPreview.js
new file mode 100644
index 0000000..24a65e6
--- /dev/null
+++ b/src/js/view/mxPrintPreview.js
@@ -0,0 +1,801 @@
+/**
+ * $Id: mxPrintPreview.js,v 1.61 2012-05-15 14:12:40 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPrintPreview
+ *
+ * Implements printing of a diagram across multiple pages. The following opens
+ * a print preview for an existing graph:
+ *
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.open();
+ * (end)
+ *
+ * Use <mxUtils.getScaleForPageCount> as follows in order to print the graph
+ * across a given number of pages:
+ *
+ * (code)
+ * var pageCount = mxUtils.prompt('Enter page count', '1');
+ *
+ * if (pageCount != null)
+ * {
+ * var scale = mxUtils.getScaleForPageCount(pageCount, graph);
+ * var preview = new mxPrintPreview(graph, scale);
+ * preview.open();
+ * }
+ * (end)
+ *
+ * Headers:
+ *
+ * Apart from setting the title argument in the mxPrintPreview constructor you
+ * can override <renderPage> as follows to add a header to any page:
+ *
+ * (code)
+ * var oldRenderPage = mxPrintPreview.prototype.renderPage;
+ * mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, scale, 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 - Page ' + pageNumber + ' / ' + this.pageCount);
+ * div.firstChild.appendChild(header);
+ *
+ * return div;
+ * };
+ * (end)
+ *
+ * Page Format:
+ *
+ * For landscape printing, use <mxConstants.PAGE_FORMAT_A4_LANDSCAPE> as
+ * the pageFormat in <mxUtils.getScaleForPageCount> and <mxPrintPreview>.
+ * Keep in mind that one can not set the defaults for the print dialog
+ * of the operating system from JavaScript so the user must manually choose
+ * a page format that matches this setting.
+ *
+ * You can try passing the following CSS directive to <open> to set the
+ * page format in the print dialog to landscape. However, this CSS
+ * directive seems to be ignored in most major browsers, including IE.
+ *
+ * (code)
+ * @page {
+ * size: landscape;
+ * }
+ * (end)
+ *
+ * Note that the print preview behaves differently in IE when used from the
+ * filesystem or via HTTP so printing should always be tested via HTTP.
+ *
+ * If you are using a DOCTYPE in the source page you can override <getDoctype>
+ * and provide the same DOCTYPE for the print preview if required. Here is
+ * an example for IE8 standards mode.
+ *
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.getDoctype = function()
+ * {
+ * return '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=8" ><![endif]-->';
+ * };
+ * preview.open();
+ * (end)
+ *
+ * Constructor: mxPrintPreview
+ *
+ * Constructs a new print preview for the given parameters.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to be previewed.
+ * scale - Optional scale of the output. Default is 1 / <mxGraph.pageScale>.
+ * 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.
+ * pageFormat - <mxRectangle> that specifies the page format (in pixels).
+ * This should match the page format of the printer. Default uses the
+ * <mxGraph.pageFormat> of the given graph.
+ * x0 - Optional left offset of the output. Default is 0.
+ * y0 - Optional top offset of the output. Default is 0.
+ * borderColor - Optional color of the page border. Default is no border.
+ * Note that a border is sometimes useful to highlight the printed page
+ * border in the print preview of the browser.
+ * title - Optional string that is used for the window title. Default
+ * is 'Printer-friendly version'.
+ * pageSelector - Optional boolean that specifies if the page selector
+ * should appear in the window with the print preview. Default is true.
+ */
+function mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector)
+{
+ this.graph = graph;
+ this.scale = (scale != null) ? scale : 1 / graph.pageScale;
+ this.border = (border != null) ? border : 0;
+ this.pageFormat = (pageFormat != null) ? pageFormat : graph.pageFormat;
+ this.title = (title != null) ? title : 'Printer-friendly version';
+ this.x0 = (x0 != null) ? x0 : 0;
+ this.y0 = (y0 != null) ? y0 : 0;
+ this.borderColor = borderColor;
+ this.pageSelector = (pageSelector != null) ? pageSelector : true;
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the <mxGraph> that should be previewed.
+ */
+mxPrintPreview.prototype.graph = null;
+
+/**
+ * Variable: pageFormat
+ *
+ * Holds the <mxRectangle> that defines the page format.
+ */
+mxPrintPreview.prototype.pageFormat = null;
+
+/**
+ * Variable: scale
+ *
+ * Holds the scale of the print preview.
+ */
+mxPrintPreview.prototype.scale = null;
+
+/**
+ * Variable: border
+ *
+ * The border inset around each side of every page in the preview. This is set
+ * to 0 if autoOrigin is false.
+ */
+mxPrintPreview.prototype.border = 0;
+
+/**
+/**
+ * Variable: 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. If this is set to false then the
+ * values for <x0> and <y0> will be overridden in <open>. Default is true.
+ */
+mxPrintPreview.prototype.autoOrigin = true;
+
+/**
+ * Variable: printOverlays
+ *
+ * Specifies if overlays should be printed. Default is false.
+ */
+mxPrintPreview.prototype.printOverlays = false;
+
+/**
+ * Variable: 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: pageCount
+ *
+ * Holds the actual number of pages in the preview.
+ */
+mxPrintPreview.prototype.pageCount = 0;
+
+/**
+ * Function: getWindow
+ *
+ * Returns <wnd>.
+ */
+mxPrintPreview.prototype.getWindow = function()
+{
+ return this.wnd;
+};
+
+/**
+ * Function: getDocType
+ *
+ * Returns the string that should go before the HTML tag in the print preview
+ * page. This implementation returns an empty string.
+ */
+mxPrintPreview.prototype.getDoctype = function()
+{
+ return '';
+};
+
+/**
+ * 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 new page's head section.
+ */
+mxPrintPreview.prototype.open = function(css)
+{
+ // 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.wnd == null)
+ {
+ this.wnd = window.open();
+ var doc = this.wnd.document;
+ var dt = this.getDoctype();
+
+ if (dt != null && dt.length > 0)
+ {
+ doc.writeln(dt);
+ }
+
+ doc.writeln('<html>');
+ doc.writeln('<head>');
+ this.writeHead(doc, css);
+ doc.writeln('</head>');
+ doc.writeln('<body class="mxPage">');
+
+ // Adds all required stylesheets and namespaces
+ mxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc);
+
+ if (mxClient.IS_IE && document.documentMode != 9)
+ {
+ doc.namespaces.add('v', 'urn:schemas-microsoft-com:vml');
+ doc.namespaces.add('o', 'urn:schemas-microsoft-com:office:office');
+ var ss = doc.createStyleSheet();
+ ss.cssText = 'v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}';
+ mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css', doc);
+ }
+
+ // 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;
+ }
+
+ // Compute the unscaled, untranslated bounds to find
+ // the number of vertical and horizontal pages
+ bounds.width /= sc;
+ bounds.height /= sc;
+
+ // Store the available page area
+ var availableWidth = this.pageFormat.width - (this.border * 2);
+ var availableHeight = this.pageFormat.height - (this.border * 2);
+
+ 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);
+
+ // Workaround for position: fixed which isn't working in IE
+ if (mxClient.IS_IE)
+ {
+ table.style.position = 'absolute';
+
+ var update = function()
+ {
+ table.style.top = (doc.body.scrollTop + 10) + 'px';
+ };
+
+ mxEvent.addListener(this.wnd, 'scroll', function(evt)
+ {
+ update();
+ });
+
+ mxEvent.addListener(this.wnd, 'resize', function(evt)
+ {
+ update();
+ });
+ }
+ }
+ });
+
+ // Stores pages for later retrieval
+ var pages = null;
+
+ // Workaround for aspect of image shapes updated asynchronously
+ // in VML so we need to fetch the markup of the DIV containing
+ // the image after the udpate of the style of the DOM node.
+ // LATER: Allow document for display markup to be customized.
+ if (mxClient.IS_IE && document.documentMode != 9)
+ {
+ pages = [];
+
+ // Overrides asynchronous loading of images for fetching HTML markup
+ var waitCounter = 0;
+ var isDone = false;
+
+ var mxImageShapeScheduleUpdateAspect = mxImageShape.prototype.scheduleUpdateAspect;
+ var mxImageShapeUpdateAspect = mxImageShape.prototype.updateAspect;
+
+ var writePages = function()
+ {
+ if (isDone && waitCounter == 0)
+ {
+ // Restores previous implementations
+ mxImageShape.prototype.scheduleUpdateAspect = mxImageShapeScheduleUpdateAspect;
+ mxImageShape.prototype.updateAspect = mxImageShapeUpdateAspect;
+
+ var markup = '';
+
+ for (var i = 0; i < pages.length; i++)
+ {
+ markup += pages[i].outerHTML;
+ pages[i].parentNode.removeChild(pages[i]);
+
+ if (i < pages.length - 1)
+ {
+ markup += '<hr/>';
+ }
+ }
+
+ doc.body.innerHTML = markup;
+ writePageSelector();
+ }
+ };
+
+ // Overrides functions to implement wait counter
+ mxImageShape.prototype.scheduleUpdateAspect = function()
+ {
+ waitCounter++;
+ mxImageShapeScheduleUpdateAspect.apply(this, arguments);
+ };
+
+ // Overrides functions to implement wait counter
+ mxImageShape.prototype.updateAspect = function()
+ {
+ mxImageShapeUpdateAspect.apply(this, arguments);
+ waitCounter--;
+ writePages();
+ };
+ }
+
+ // 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;
+
+ div = this.renderPage(this.pageFormat.width, this.pageFormat.height,
+ -dx, -dy, this.scale, pageNum);
+
+ // Gives the page a unique ID for later accessing the page
+ div.setAttribute('id', 'mxPage-'+pageNum);
+
+ // 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 = 'white';
+
+ if (i < vpages - 1 || j < hpages - 1)
+ {
+ 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 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 (mxClient.IS_IE)
+ {
+ // 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.
+ // For delayed output we remote the DIV from the
+ // original document when we write out all pages.
+ doc.writeln(div.outerHTML);
+
+ if (pages != null)
+ {
+ pages.push(div);
+ }
+ else
+ {
+ div.parentNode.removeChild(div);
+ }
+ }
+ else
+ {
+ div.parentNode.removeChild(div);
+ doc.body.appendChild(div);
+ }
+
+ if (i < vpages - 1 || j < hpages - 1)
+ {
+ var hr = doc.createElement('hr');
+ hr.className = 'mxPageBreak';
+ doc.body.appendChild(hr);
+ }
+ }
+ }
+
+ doc.writeln('</body>');
+ doc.writeln('</html>');
+ doc.close();
+
+ // Marks the printing complete for async handling
+ if (pages != null)
+ {
+ isDone = true;
+ writePages();
+ }
+ else
+ {
+ writePageSelector();
+ }
+
+ // Removes all event handlers in the print output
+ mxEvent.release(doc.body);
+ }
+
+ 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: writeHead
+ *
+ * Writes the HEAD section into the given document, without the opening
+ * and closing HEAD tags.
+ */
+mxPrintPreview.prototype.writeHead = function(doc, css)
+{
+ if (this.title != null)
+ {
+ doc.writeln('<title>' + this.title + '</title>');
+ }
+
+ // Makes sure no horizontal rulers are printed
+ doc.writeln('<style type="text/css">');
+ doc.writeln('@media print {');
+ doc.writeln(' table.mxPageSelector { display: none; }');
+ doc.writeln(' hr.mxPageBreak { display: none; }');
+ doc.writeln('}');
+ doc.writeln('@media screen {');
+
+ // NOTE: position: fixed is not supported in IE, so the page selector
+ // position (absolute) needs to be updated in IE (see below)
+ doc.writeln(' table.mxPageSelector { position: fixed; right: 10px; top: 10px;' +
+ 'font-family: Arial; font-size:10pt; border: solid 1px darkgray;' +
+ 'background: white; border-collapse:collapse; }');
+ doc.writeln(' table.mxPageSelector td { border: solid 1px gray; padding:4px; }');
+ doc.writeln(' body.mxPage { background: gray; }');
+ doc.writeln('}');
+
+ if (css != null)
+ {
+ doc.writeln(css);
+ }
+
+ doc.writeln('</style>');
+};
+
+/**
+ * Function: 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');
+
+ // Needs anchor for all browers to work without JavaScript
+ // LATER: Does not work in Firefox because the generated document
+ // has the URL of the opening document, the anchor is appended
+ // to that URL and the full URL is loaded on click.
+ if (!mxClient.IS_NS || mxClient.IS_SF || mxClient.IS_GC)
+ {
+ var a = doc.createElement('a');
+ a.setAttribute('href', '#mxPage-' + pageNum);
+ mxUtils.write(a, pageNum, doc);
+ cell.appendChild(a);
+ }
+ else
+ {
+ mxUtils.write(cell, pageNum, doc);
+ }
+
+ 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 - Horizontal translation for the diagram.
+ * dy - Vertical translation for the diagram.
+ * scale - Scale for the diagram.
+ * pageNumber - Number of the page to be rendered.
+ */
+mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, scale, pageNumber)
+{
+ var div = document.createElement('div');
+
+ try
+ {
+ div.style.width = w + 'px';
+ div.style.height = h + 'px';
+ div.style.overflow = 'hidden';
+ div.style.pageBreakInside = 'avoid';
+
+ var innerDiv = document.createElement('div');
+ 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';
+
+ if (this.graph.dialect == mxConstants.DIALECT_VML)
+ {
+ innerDiv.style.position = 'absolute';
+ }
+
+ div.appendChild(innerDiv);
+ document.body.appendChild(div);
+ var view = this.graph.getView();
+
+ var previousContainer = this.graph.container;
+ this.graph.container = innerDiv;
+
+ var canvas = view.getCanvas();
+ var backgroundPane = view.getBackgroundPane();
+ var drawPane = view.getDrawPane();
+ var overlayPane = view.getOverlayPane();
+
+ if (this.graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ view.createSvg();
+ }
+ 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);
+
+ var temp = null;
+
+ try
+ {
+ // Creates the temporary cell states in the view and
+ // draws them onto the temporary DOM nodes in the view
+ var model = this.graph.getModel();
+ var cells = [model.getRoot()];
+ temp = new mxTemporaryCellStates(view, scale, cells);
+ }
+ finally
+ {
+ // Removes overlay pane with selection handles
+ // controls and icons from the print output
+ if (mxClient.IS_IE)
+ {
+ view.overlayPane.innerHTML = '';
+ }
+ else
+ {
+ // Removes everything but the SVG node
+ var tmp = innerDiv.firstChild;
+
+ while (tmp != null)
+ {
+ var next = tmp.nextSibling;
+ var name = tmp.nodeName.toLowerCase();
+
+ // Note: Width and heigh are required in FF 11
+ if (name == 'svg')
+ {
+ tmp.setAttribute('width', parseInt(innerDiv.style.width));
+ tmp.setAttribute('height', parseInt(innerDiv.style.height));
+ }
+ // Tries to fetch all text labels and only text labels
+ else if (tmp.style.cursor != 'default' && name != 'table')
+ {
+ tmp.parentNode.removeChild(tmp);
+ }
+
+ tmp = next;
+ }
+ }
+
+ // 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;
+ view.canvas = canvas;
+ view.backgroundPane = backgroundPane;
+ view.drawPane = drawPane;
+ view.overlayPane = overlayPane;
+ view.translate = translate;
+ temp.destroy();
+ view.setEventsEnabled(eventsEnabled);
+ }
+ }
+ catch (e)
+ {
+ div.parentNode.removeChild(div);
+ div = null;
+
+ throw e;
+ }
+
+ return div;
+};
+
+/**
+ * Function: print
+ *
+ * Opens the print preview and shows the print dialog.
+ */
+mxPrintPreview.prototype.print = function()
+{
+ var wnd = this.open();
+
+ 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;
+ }
+};
diff --git a/src/js/view/mxSpaceManager.js b/src/js/view/mxSpaceManager.js
new file mode 100644
index 0000000..2a2dd11
--- /dev/null
+++ b/src/js/view/mxSpaceManager.js
@@ -0,0 +1,460 @@
+/**
+ * $Id: mxSpaceManager.js,v 1.9 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSpaceManager
+ *
+ * In charge of moving cells after a resize.
+ *
+ * Constructor: mxSpaceManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxSpaceManager(graph, shiftRightwards, shiftDownwards, extendParents)
+{
+ this.resizeHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.cellsResized(evt.getProperty('cells'));
+ }
+ });
+
+ this.foldHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.cellsResized(evt.getProperty('cells'));
+ }
+ });
+
+ this.shiftRightwards = (shiftRightwards != null) ? shiftRightwards : true;
+ this.shiftDownwards = (shiftDownwards != null) ? shiftDownwards : true;
+ this.extendParents = (extendParents != null) ? extendParents : true;
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSpaceManager.prototype = new mxEventSource();
+mxSpaceManager.prototype.constructor = mxSpaceManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSpaceManager.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.enabled = true;
+
+/**
+ * Variable: shiftRightwards
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.shiftRightwards = true;
+
+/**
+ * Variable: shiftDownwards
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.shiftDownwards = true;
+
+/**
+ * Variable: extendParents
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.extendParents = true;
+
+/**
+ * Variable: resizeHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxSpaceManager.prototype.resizeHandler = null;
+
+/**
+ * Variable: foldHandler
+ *
+ * Holds the function that handles the fold event.
+ */
+mxSpaceManager.prototype.foldHandler = null;
+
+/**
+ * Function: isCellIgnored
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxSpaceManager.prototype.isCellIgnored = function(cell)
+{
+ return !this.getGraph().getModel().isVertex(cell);
+};
+
+/**
+ * Function: isCellShiftable
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxSpaceManager.prototype.isCellShiftable = function(cell)
+{
+ return this.getGraph().getModel().isVertex(cell) &&
+ this.getGraph().isCellMovable(cell);
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isShiftRightwards
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isShiftRightwards = function()
+{
+ return this.shiftRightwards;
+};
+
+/**
+ * Function: setShiftRightwards
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setShiftRightwards = function(value)
+{
+ this.shiftRightwards = value;
+};
+
+/**
+ * Function: isShiftDownwards
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isShiftDownwards = function()
+{
+ return this.shiftDownwards;
+};
+
+/**
+ * Function: setShiftDownwards
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setShiftDownwards = function(value)
+{
+ this.shiftDownwards = value;
+};
+
+/**
+ * Function: isExtendParents
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isExtendParents = function()
+{
+ return this.extendParents;
+};
+
+/**
+ * Function: setShiftDownwards
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setExtendParents = function(value)
+{
+ this.extendParents = value;
+};
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this layout operates on.
+ */
+mxSpaceManager.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxSpaceManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ this.graph.removeListener(this.resizeHandler);
+ this.graph.removeListener(this.foldHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ this.graph.addListener(mxEvent.RESIZE_CELLS, this.resizeHandler);
+ this.graph.addListener(mxEvent.FOLD_CELLS, this.foldHandler);
+ }
+};
+
+/**
+ * Function: cellsResized
+ *
+ * Called from <moveCellsIntoParent> to invoke the <move> hook in the
+ * automatic layout of each modified cell's parent. The event is used to
+ * define the x- and y-coordinates passed to the move function.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been resized.
+ */
+mxSpaceManager.prototype.cellsResized = function(cells)
+{
+ if (cells != null)
+ {
+ var model = this.graph.getModel();
+
+ // Raising the update level should not be required
+ // since only one call is made below
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isCellIgnored(cells[i]))
+ {
+ this.cellResized(cells[i]);
+ break;
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: cellResized
+ *
+ * Called from <moveCellsIntoParent> to invoke the <move> hook in the
+ * automatic layout of each modified cell's parent. The event is used to
+ * define the x- and y-coordinates passed to the move function.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that has been resized.
+ */
+mxSpaceManager.prototype.cellResized = function(cell)
+{
+ var graph = this.getGraph();
+ var view = graph.getView();
+ var model = graph.getModel();
+
+ var state = view.getState(cell);
+ var pstate = view.getState(model.getParent(cell));
+
+ if (state != null &&
+ pstate != null)
+ {
+ var cells = this.getCellsToShift(state);
+ var geo = model.getGeometry(cell);
+
+ if (cells != null &&
+ geo != null)
+ {
+ var tr = view.translate;
+ var scale = view.scale;
+
+ var x0 = state.x - pstate.origin.x - tr.x * scale;
+ var y0 = state.y - pstate.origin.y - tr.y * scale;
+ var right = state.x + state.width;
+ var bottom = state.y + state.height;
+
+ var dx = state.width - geo.width * scale + x0 - geo.x * scale;
+ var dy = state.height - geo.height * scale + y0 - geo.y * scale;
+
+ var fx = 1 - geo.width * scale / state.width;
+ var fy = 1 - geo.height * scale / state.height;
+
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != cell &&
+ this.isCellShiftable(cells[i]))
+ {
+ this.shiftCell(cells[i], dx, dy, x0, y0, right, bottom, fx, fy,
+ this.isExtendParents() &&
+ graph.isExtendParent(cells[i]));
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ }
+};
+
+/**
+ * Function: shiftCell
+ *
+ * Called from <moveCellsIntoParent> to invoke the <move> hook in the
+ * automatic layout of each modified cell's parent. The event is used to
+ * define the x- and y-coordinates passed to the move function.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxSpaceManager.prototype.shiftCell = function(cell, dx, dy, Ox0, y0, right,
+ bottom, fx, fy, extendParent)
+{
+ var graph = this.getGraph();
+ var state = graph.getView().getState(cell);
+
+ if (state != null)
+ {
+ var model = graph.getModel();
+ var geo = model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ model.beginUpdate();
+ try
+ {
+ if (this.isShiftRightwards())
+ {
+ if (state.x >= right)
+ {
+ geo = geo.clone();
+ geo.translate(-dx, 0);
+ }
+ else
+ {
+ var tmpDx = Math.max(0, state.x - x0);
+ geo = geo.clone();
+ geo.translate(-fx * tmpDx, 0);
+ }
+ }
+
+ if (this.isShiftDownwards())
+ {
+ if (state.y >= bottom)
+ {
+ geo = geo.clone();
+ geo.translate(0, -dy);
+ }
+ else
+ {
+ var tmpDy = Math.max(0, state.y - y0);
+ geo = geo.clone();
+ geo.translate(0, -fy * tmpDy);
+ }
+ }
+
+ if (geo != model.getGeometry(cell))
+ {
+ model.setGeometry(cell, geo);
+
+ // Parent size might need to be updated if this
+ // is seen as part of the resize
+ if (extendParent)
+ {
+ graph.extendParent(cell);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ }
+};
+
+/**
+ * Function: getCellsToShift
+ *
+ * Returns the cells to shift after a resize of the
+ * specified <mxCellState>.
+ */
+mxSpaceManager.prototype.getCellsToShift = function(state)
+{
+ var graph = this.getGraph();
+ var parent = graph.getModel().getParent(state.cell);
+ var down = this.isShiftDownwards();
+ var right = this.isShiftRightwards();
+
+ return graph.getCellsBeyond(state.x + ((down) ? 0 : state.width),
+ state.y + ((down && right) ? 0 : state.height), parent, right, down);
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxSpaceManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/view/mxStyleRegistry.js b/src/js/view/mxStyleRegistry.js
new file mode 100644
index 0000000..6ad878d
--- /dev/null
+++ b/src/js/view/mxStyleRegistry.js
@@ -0,0 +1,70 @@
+/**
+ * $Id: mxStyleRegistry.js,v 1.10 2011-04-27 10:15:39 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxStyleRegistry =
+{
+ /**
+ * Class: mxStyleRegistry
+ *
+ * Singleton class that acts as a global converter from string to object values
+ * in a style. This is currently only used to perimeters and edge styles.
+ *
+ * Variable: values
+ *
+ * Maps from strings to objects.
+ */
+ values: [],
+
+ /**
+ * Function: putValue
+ *
+ * Puts the given object into the registry under the given name.
+ */
+ putValue: function(name, obj)
+ {
+ mxStyleRegistry.values[name] = obj;
+ },
+
+ /**
+ * Function: getValue
+ *
+ * Returns the value associated with the given name.
+ */
+ getValue: function(name)
+ {
+ return mxStyleRegistry.values[name];
+ },
+
+ /**
+ * Function: getName
+ *
+ * Returns the name for the given value.
+ */
+ getName: function(value)
+ {
+ for (var key in mxStyleRegistry.values)
+ {
+ if (mxStyleRegistry.values[key] == value)
+ {
+ return key;
+ }
+ }
+
+ return null;
+ }
+
+};
+
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW, mxEdgeStyle.ElbowConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION, mxEdgeStyle.EntityRelation);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP, mxEdgeStyle.Loop);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE, mxEdgeStyle.SideToSide);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM, mxEdgeStyle.TopToBottom);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL, mxEdgeStyle.OrthConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT, mxEdgeStyle.SegmentConnector);
+
+mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE, mxPerimeter.EllipsePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE, mxPerimeter.RectanglePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS, mxPerimeter.RhombusPerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE, mxPerimeter.TrianglePerimeter);
diff --git a/src/js/view/mxStylesheet.js b/src/js/view/mxStylesheet.js
new file mode 100644
index 0000000..82a520e
--- /dev/null
+++ b/src/js/view/mxStylesheet.js
@@ -0,0 +1,266 @@
+/**
+ * $Id: mxStylesheet.js,v 1.35 2010-03-26 10:24:58 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxStylesheet
+ *
+ * Defines the appearance of the cells in a graph. See <putCellStyle> for an
+ * example of creating a new cell style. It is recommended to use objects, not
+ * arrays for holding cell styles. Existing styles can be cloned using
+ * <mxUtils.clone> and turned into a string for debugging using
+ * <mxUtils.toString>.
+ *
+ * Default Styles:
+ *
+ * The stylesheet contains two built-in styles, which are used if no style is
+ * defined for a cell:
+ *
+ * defaultVertex - Default style for vertices
+ * defaultEdge - Default style for edges
+ *
+ * Example:
+ *
+ * (code)
+ * var vertexStyle = stylesheet.getDefaultVertexStyle();
+ * vertexStyle[mxConstants.ROUNDED] = true;
+ * var edgeStyle = stylesheet.getDefaultEdgeStyle();
+ * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;
+ * (end)
+ *
+ * Modifies the built-in default styles.
+ *
+ * To avoid the default style for a cell, add a leading semicolon
+ * to the style definition, eg.
+ *
+ * (code)
+ * ;shadow=1
+ * (end)
+ *
+ * Removing keys:
+ *
+ * For removing a key in a cell style of the form [stylename;|key=value;] the
+ * special value none can be used, eg. highlight;fillColor=none
+ *
+ * See also the helper methods in mxUtils to modify strings of this format,
+ * namely <mxUtils.setStyle>, <mxUtils.indexOfStylename>,
+ * <mxUtils.addStylename>, <mxUtils.removeStylename>,
+ * <mxUtils.removeAllStylenames> and <mxUtils.setStyleFlag>.
+ *
+ * Constructor: mxStylesheet
+ *
+ * Constructs a new stylesheet and assigns default styles.
+ */
+function mxStylesheet()
+{
+ this.styles = new Object();
+
+ this.putDefaultVertexStyle(this.createDefaultVertexStyle());
+ this.putDefaultEdgeStyle(this.createDefaultEdgeStyle());
+};
+
+/**
+ * Function: styles
+ *
+ * Maps from names to cell styles. Each cell style is a map of key,
+ * value pairs.
+ */
+mxStylesheet.prototype.styles;
+
+/**
+ * Function: createDefaultVertexStyle
+ *
+ * Creates and returns the default vertex style.
+ */
+mxStylesheet.prototype.createDefaultVertexStyle = function()
+{
+ var style = new Object();
+
+ style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+ style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+ style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+ style[mxConstants.STYLE_FILLCOLOR] = '#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
+ * <styles>.
+ *
+ * Example:
+ *
+ * The following example adds a new style called 'rounded' into an
+ * existing stylesheet:
+ *
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ * style[mxConstants.STYLE_ROUNDED] = true;
+ * graph.getStylesheet().putCellStyle('rounded', style);
+ * (end)
+ *
+ * In the above example, the new style is an object. The possible keys of
+ * the object are all the constants in <mxConstants> that start with STYLE
+ * and the values are either JavaScript objects, such as
+ * <mxPerimeter.RightAngleRectanglePerimeter> (which is in fact a function)
+ * or expressions, such as true. Note that not all keys will be
+ * interpreted by all shapes (eg. the line shape ignores the fill color).
+ * The final call to this method associates the style with a name in the
+ * stylesheet. The style is used in a cell with the following code:
+ *
+ * (code)
+ * model.setStyle(cell, 'rounded');
+ * (end)
+ *
+ * Parameters:
+ *
+ * name - Name for the style to be stored.
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putCellStyle = function(name, style)
+{
+ this.styles[name] = style;
+};
+
+/**
+ * Function: getCellStyle
+ *
+ * Returns the cell style for the specified stylename or the given
+ * defaultStyle if no style can be found for the given stylename.
+ *
+ * Parameters:
+ *
+ * name - String of the form [(stylename|key=value);] that represents the
+ * style.
+ * defaultStyle - Default style to be returned if no style can be found.
+ */
+mxStylesheet.prototype.getCellStyle = function(name, defaultStyle)
+{
+ var style = defaultStyle;
+
+ if (name != null && name.length > 0)
+ {
+ var pairs = name.split(';');
+
+ if (style != null &&
+ name.charAt(0) != ';')
+ {
+ style = mxUtils.clone(style);
+ }
+ else
+ {
+ style = new Object();
+ }
+
+ // Parses each key, value pair into the existing style
+ for (var i = 0; i < pairs.length; i++)
+ {
+ var tmp = pairs[i];
+ var pos = tmp.indexOf('=');
+
+ if (pos >= 0)
+ {
+ var key = tmp.substring(0, pos);
+ var value = tmp.substring(pos + 1);
+
+ if (value == mxConstants.NONE)
+ {
+ delete style[key];
+ }
+ else if (mxUtils.isNumeric(value))
+ {
+ style[key] = parseFloat(value);
+ }
+ else
+ {
+ style[key] = value;
+ }
+ }
+ else
+ {
+ // Merges the entries from a named style
+ var tmpStyle = this.styles[tmp];
+
+ if (tmpStyle != null)
+ {
+ for (var key in tmpStyle)
+ {
+ style[key] = tmpStyle[key];
+ }
+ }
+ }
+ }
+ }
+
+ return style;
+};
diff --git a/src/js/view/mxSwimlaneManager.js b/src/js/view/mxSwimlaneManager.js
new file mode 100644
index 0000000..fe40613
--- /dev/null
+++ b/src/js/view/mxSwimlaneManager.js
@@ -0,0 +1,449 @@
+/**
+ * $Id: mxSwimlaneManager.js,v 1.17 2011-01-14 15:21:10 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSwimlaneManager
+ *
+ * Manager for swimlanes and nested swimlanes that sets the size of newly added
+ * swimlanes to that of their siblings, and propagates changes to the size of a
+ * swimlane to its siblings, if <siblings> is true, and its ancestors, if
+ * <bubbling> is true.
+ *
+ * Constructor: mxSwimlaneManager
+ *
+ * Constructs a new swimlane manager for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxSwimlaneManager(graph, horizontal, addEnabled, resizeEnabled)
+{
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.addEnabled = (addEnabled != null) ? addEnabled : true;
+ this.resizeEnabled = (resizeEnabled != null) ? resizeEnabled : true;
+
+ this.addHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled() && this.isAddEnabled())
+ {
+ this.cellsAdded(evt.getProperty('cells'));
+ }
+ });
+
+ this.resizeHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled() && this.isResizeEnabled())
+ {
+ this.cellsResized(evt.getProperty('cells'));
+ }
+ });
+
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSwimlaneManager.prototype = new mxEventSource();
+mxSwimlaneManager.prototype.constructor = mxSwimlaneManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSwimlaneManager.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSwimlaneManager.prototype.enabled = true;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the swimlanes. Default is true.
+ */
+mxSwimlaneManager.prototype.horizontal = true;
+
+/**
+ * Variable: addEnabled
+ *
+ * Specifies if newly added cells should be resized to match the size of their
+ * existing siblings. Default is true.
+ */
+mxSwimlaneManager.prototype.addEnabled = true;
+
+/**
+ * Variable: resizeEnabled
+ *
+ * Specifies if resizing of swimlanes should be handled. Default is true.
+ */
+mxSwimlaneManager.prototype.resizeEnabled = true;
+
+/**
+ * Variable: moveHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.addHandler = null;
+
+/**
+ * Variable: moveHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.resizeHandler = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSwimlaneManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSwimlaneManager.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isHorizontal
+ *
+ * Returns <horizontal>.
+ */
+mxSwimlaneManager.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: setHorizontal
+ *
+ * Sets <horizontal>.
+ */
+mxSwimlaneManager.prototype.setHorizontal = function(value)
+{
+ this.horizontal = value;
+};
+
+/**
+ * Function: isAddEnabled
+ *
+ * Returns <addEnabled>.
+ */
+mxSwimlaneManager.prototype.isAddEnabled = function()
+{
+ return this.addEnabled;
+};
+
+/**
+ * Function: setAddEnabled
+ *
+ * Sets <addEnabled>.
+ */
+mxSwimlaneManager.prototype.setAddEnabled = function(value)
+{
+ this.addEnabled = value;
+};
+
+/**
+ * Function: isResizeEnabled
+ *
+ * Returns <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.isResizeEnabled = function()
+{
+ return this.resizeEnabled;
+};
+
+/**
+ * Function: setResizeEnabled
+ *
+ * Sets <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.setResizeEnabled = function(value)
+{
+ this.resizeEnabled = value;
+};
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this manager operates on.
+ */
+mxSwimlaneManager.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the manager operates on.
+ */
+mxSwimlaneManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ this.graph.removeListener(this.addHandler);
+ this.graph.removeListener(this.resizeHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ this.graph.addListener(mxEvent.ADD_CELLS, this.addHandler);
+ this.graph.addListener(mxEvent.CELLS_RESIZED, this.resizeHandler);
+ }
+};
+
+/**
+ * Function: isSwimlaneIgnored
+ *
+ * Returns true if the given swimlane should be ignored.
+ */
+mxSwimlaneManager.prototype.isSwimlaneIgnored = function(swimlane)
+{
+ return !this.getGraph().isSwimlane(swimlane);
+};
+
+/**
+ * Function: isCellHorizontal
+ *
+ * Returns true if the given cell is horizontal. If the given cell is not a
+ * swimlane, then the global orientation is returned.
+ */
+mxSwimlaneManager.prototype.isCellHorizontal = function(cell)
+{
+ if (this.graph.isSwimlane(cell))
+ {
+ var state = this.graph.view.getState(cell);
+ var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+
+ return mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
+ }
+
+ return !this.isHorizontal();
+};
+
+/**
+ * Function: cellsAdded
+ *
+ * Called if any cells have been added.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been added.
+ */
+mxSwimlaneManager.prototype.cellsAdded = function(cells)
+{
+ if (cells != null)
+ {
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isSwimlaneIgnored(cells[i]))
+ {
+ this.swimlaneAdded(cells[i]);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: swimlaneAdded
+ *
+ * Updates the size of the given swimlane to match that of any existing
+ * siblings swimlanes.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> that represents the new swimlane.
+ */
+mxSwimlaneManager.prototype.swimlaneAdded = function(swimlane)
+{
+ var model = this.getGraph().getModel();
+ var parent = model.getParent(swimlane);
+ var childCount = model.getChildCount(parent);
+ var geo = null;
+
+ // Finds the first valid sibling swimlane as reference
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (child != swimlane && !this.isSwimlaneIgnored(child))
+ {
+ geo = model.getGeometry(child);
+
+ if (geo != null)
+ {
+ break;
+ }
+ }
+ }
+
+ // Applies the size of the refernece to the newly added swimlane
+ if (geo != null)
+ {
+ this.resizeSwimlane(swimlane, geo.width, geo.height);
+ }
+};
+
+/**
+ * Function: cellsResized
+ *
+ * Called if any cells have been resizes. Calls <swimlaneResized> for all
+ * swimlanes where <isSwimlaneIgnored> returns false.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose size was changed.
+ */
+mxSwimlaneManager.prototype.cellsResized = function(cells)
+{
+ if (cells != null)
+ {
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ // Finds the top-level swimlanes and adds offsets
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isSwimlaneIgnored(cells[i]))
+ {
+ var geo = model.getGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var size = new mxRectangle(0, 0, geo.width, geo.height);
+ var top = cells[i];
+ var current = top;
+
+ while (current != null)
+ {
+ top = current;
+ current = model.getParent(current);
+ var tmp = (this.graph.isSwimlane(current)) ?
+ this.graph.getStartSize(current) :
+ new mxRectangle();
+ size.width += tmp.width;
+ size.height += tmp.height;
+ }
+
+ this.resizeSwimlane(top, size.width, size.height);
+ }
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: resizeSwimlane
+ *
+ * Called from <cellsResized> for all swimlanes that are not ignored to update
+ * the size of the siblings and the size of the parent swimlanes, recursively,
+ * if <bubbling> is true.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> whose size has changed.
+ */
+mxSwimlaneManager.prototype.resizeSwimlane = function(swimlane, w, h)
+{
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ if (!this.isSwimlaneIgnored(swimlane))
+ {
+ var geo = model.getGeometry(swimlane);
+
+ if (geo != null)
+ {
+ var horizontal = this.isCellHorizontal(swimlane);
+
+ if ((horizontal && geo.height != h) || (!horizontal && geo.width != w))
+ {
+ geo = geo.clone();
+
+ if (horizontal)
+ {
+ geo.height = h;
+ }
+ else
+ {
+ geo.width = w;
+ }
+
+ model.setGeometry(swimlane, geo);
+ }
+ }
+ }
+
+ var tmp = (this.graph.isSwimlane(swimlane)) ?
+ this.graph.getStartSize(swimlane) :
+ new mxRectangle();
+ w -= tmp.width;
+ h -= tmp.height;
+
+ var childCount = model.getChildCount(swimlane);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(swimlane, i);
+ this.resizeSwimlane(child, w, h);
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxSwimlaneManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/view/mxTemporaryCellStates.js b/src/js/view/mxTemporaryCellStates.js
new file mode 100644
index 0000000..ce8232c
--- /dev/null
+++ b/src/js/view/mxTemporaryCellStates.js
@@ -0,0 +1,105 @@
+/**
+ * $Id: mxTemporaryCellStates.js,v 1.10 2010-04-20 14:43:12 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxTemporaryCellStates
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ *
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxTemporaryCellStates(view, scale, cells)
+{
+ this.view = view;
+ scale = (scale != null) ? scale : 1;
+
+ // Stores the previous state
+ this.oldBounds = view.getGraphBounds();
+ this.oldStates = view.getStates();
+ this.oldScale = view.getScale();
+
+ // Creates space for new states
+ view.setStates(new mxDictionary());
+ view.setScale(scale);
+
+ if (cells != null)
+ {
+ // Creates virtual parent state for validation
+ var state = view.createState(new mxCell());
+
+ // Validates the vertices and edges without adding them to
+ // the model so that the original cells are not modified
+ for (var i = 0; i < cells.length; i++)
+ {
+ view.validateBounds(state, cells[i]);
+ }
+
+ var bbox = null;
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var bounds = view.validatePoints(state, cells[i]);
+
+ if (bbox == null)
+ {
+ bbox = bounds;
+ }
+ else
+ {
+ bbox.add(bounds);
+ }
+ }
+
+ if (bbox == null)
+ {
+ bbox = new mxRectangle();
+ }
+
+ view.setGraphBounds(bbox);
+ }
+};
+
+/**
+ * Variable: view
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.view = null;
+
+/**
+ * Variable: oldStates
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldStates = null;
+
+/**
+ * Variable: oldBounds
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldBounds = null;
+
+/**
+ * Variable: oldScale
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldScale = null;
+
+/**
+ * Function: destroy
+ *
+ * Returns the top, left corner as a new <mxPoint>.
+ */
+mxTemporaryCellStates.prototype.destroy = function()
+{
+ this.view.setScale(this.oldScale);
+ this.view.setStates(this.oldStates);
+ this.view.setGraphBounds(this.oldBounds);
+};
diff --git a/details.js b/src/js/xcos/core/details.js
index fbeffda..fbeffda 100644
--- a/details.js
+++ b/src/js/xcos/core/details.js
diff --git a/resources/editor.properties b/src/resources/editor.properties
index 23432a8..23432a8 100644
--- a/resources/editor.properties
+++ b/src/resources/editor.properties
diff --git a/resources/graph.properties b/src/resources/graph.properties
index baf61f8..baf61f8 100644
--- a/resources/graph.properties
+++ b/src/resources/graph.properties
diff --git a/test.html b/test.html
index 932c29f..0f070de 100644
--- a/test.html
+++ b/test.html
@@ -1,7 +1,7 @@
<html>
<head>
<title>Testing Details</title>
-<script type="text/javascript" src="details.js"></script>
+<script type="text/javascript" src="src/js/xcos/core/details.js"></script>
<script type="text/javascript" src="json2.js"></script>
</head>
<body>