From 92f3207b50a1caca07df5c5b238212af3358905b Mon Sep 17 00:00:00 2001
From: adhitya
Date: Mon, 11 Apr 2016 15:10:54 +0000
Subject: Revert last two commits - Keyboard shortcuts are not working
---
css/common.css | 152 -
css/explorer.css | 15 -
details.js | 204 -
images/button.gif | Bin 137 -> 0 bytes
images/close.gif | Bin 70 -> 0 bytes
images/collapsed.gif | Bin 877 -> 0 bytes
images/error.gif | Bin 907 -> 0 bytes
images/expanded.gif | Bin 878 -> 0 bytes
images/icons48/column.png | Bin 0 -> 1787 bytes
images/icons48/earth.png | Bin 0 -> 4520 bytes
images/icons48/gear.png | Bin 0 -> 4418 bytes
images/icons48/keys.png | Bin 0 -> 4295 bytes
images/icons48/mail_new.png | Bin 0 -> 3944 bytes
images/icons48/server.png | Bin 0 -> 3556 bytes
images/icons48/table.png | Bin 0 -> 1574 bytes
images/maximize.gif | Bin 843 -> 0 bytes
images/minimize.gif | Bin 64 -> 0 bytes
images/normalize.gif | Bin 845 -> 0 bytes
images/point.gif | Bin 55 -> 0 bytes
images/resize.gif | Bin 74 -> 0 bytes
images/separator.gif | Bin 146 -> 0 bytes
images/submenu.gif | Bin 56 -> 0 bytes
images/transparent.gif | Bin 90 -> 0 bytes
images/warning.gif | Bin 276 -> 0 bytes
images/warning.png | Bin 425 -> 0 bytes
images/window-title.gif | Bin 275 -> 0 bytes
images/window.gif | Bin 75 -> 0 bytes
index.html | 12 +-
mxClient.min.js | 1564 ---
resources/editor.properties | 5 -
resources/graph.properties | 11 -
src/css/common.css | 152 +
src/css/explorer.css | 15 +
src/images/button.gif | Bin 0 -> 137 bytes
src/images/close.gif | Bin 0 -> 70 bytes
src/images/collapsed.gif | Bin 0 -> 877 bytes
src/images/error.gif | Bin 0 -> 907 bytes
src/images/expanded.gif | Bin 0 -> 878 bytes
src/images/maximize.gif | Bin 0 -> 843 bytes
src/images/minimize.gif | Bin 0 -> 64 bytes
src/images/normalize.gif | Bin 0 -> 845 bytes
src/images/point.gif | Bin 0 -> 55 bytes
src/images/resize.gif | Bin 0 -> 74 bytes
src/images/separator.gif | Bin 0 -> 146 bytes
src/images/submenu.gif | Bin 0 -> 56 bytes
src/images/transparent.gif | Bin 0 -> 90 bytes
src/images/warning.gif | Bin 0 -> 276 bytes
src/images/warning.png | Bin 0 -> 425 bytes
src/images/window-title.gif | Bin 0 -> 275 bytes
src/images/window.gif | Bin 0 -> 75 bytes
src/js/editor/mxDefaultKeyHandler.js | 126 +
src/js/editor/mxDefaultPopupMenu.js | 300 +
src/js/editor/mxDefaultToolbar.js | 567 +
src/js/editor/mxEditor.js | 3220 ++++++
src/js/handler/mxCellHighlight.js | 271 +
src/js/handler/mxCellMarker.js | 419 +
src/js/handler/mxCellTracker.js | 149 +
src/js/handler/mxConnectionHandler.js | 1969 ++++
src/js/handler/mxConstraintHandler.js | 308 +
src/js/handler/mxEdgeHandler.js | 1529 +++
src/js/handler/mxEdgeSegmentHandler.js | 284 +
src/js/handler/mxElbowEdgeHandler.js | 248 +
src/js/handler/mxGraphHandler.js | 916 ++
src/js/handler/mxKeyHandler.js | 402 +
src/js/handler/mxPanningHandler.js | 390 +
src/js/handler/mxRubberband.js | 348 +
src/js/handler/mxSelectionCellsHandler.js | 260 +
src/js/handler/mxTooltipHandler.js | 317 +
src/js/handler/mxVertexHandler.js | 753 ++
src/js/index.txt | 316 +
src/js/io/mxCellCodec.js | 170 +
src/js/io/mxChildChangeCodec.js | 149 +
src/js/io/mxCodec.js | 531 +
src/js/io/mxCodecRegistry.js | 137 +
src/js/io/mxDefaultKeyHandlerCodec.js | 88 +
src/js/io/mxDefaultPopupMenuCodec.js | 54 +
src/js/io/mxDefaultToolbarCodec.js | 301 +
src/js/io/mxEditorCodec.js | 246 +
src/js/io/mxGenericChangeCodec.js | 64 +
src/js/io/mxGraphCodec.js | 28 +
src/js/io/mxGraphViewCodec.js | 197 +
src/js/io/mxModelCodec.js | 80 +
src/js/io/mxObjectCodec.js | 983 ++
src/js/io/mxRootChangeCodec.js | 83 +
src/js/io/mxStylesheetCodec.js | 210 +
src/js/io/mxTerminalChangeCodec.js | 42 +
.../model/mxGraphAbstractHierarchyCell.js | 206 +
.../hierarchical/model/mxGraphHierarchyEdge.js | 174 +
.../hierarchical/model/mxGraphHierarchyModel.js | 685 ++
.../hierarchical/model/mxGraphHierarchyNode.js | 210 +
src/js/layout/hierarchical/mxHierarchicalLayout.js | 623 ++
.../hierarchical/stage/mxCoordinateAssignment.js | 1836 +++
.../stage/mxHierarchicalLayoutStage.js | 25 +
.../stage/mxMedianHybridCrossingReduction.js | 674 ++
.../hierarchical/stage/mxMinimumCycleRemover.js | 131 +
src/js/layout/mxCircleLayout.js | 203 +
src/js/layout/mxCompactTreeLayout.js | 995 ++
src/js/layout/mxCompositeLayout.js | 101 +
src/js/layout/mxEdgeLabelLayout.js | 165 +
src/js/layout/mxFastOrganicLayout.js | 591 +
src/js/layout/mxGraphLayout.js | 503 +
src/js/layout/mxParallelEdgeLayout.js | 198 +
src/js/layout/mxPartitionLayout.js | 240 +
src/js/layout/mxStackLayout.js | 381 +
src/js/model/mxCell.js | 806 ++
src/js/model/mxCellPath.js | 163 +
src/js/model/mxGeometry.js | 277 +
src/js/model/mxGraphModel.js | 2622 +++++
src/js/mxClient.js | 643 ++
src/js/shape/mxActor.js | 183 +
src/js/shape/mxArrow.js | 226 +
src/js/shape/mxCloud.js | 56 +
src/js/shape/mxConnector.js | 446 +
src/js/shape/mxCylinder.js | 319 +
src/js/shape/mxDoubleEllipse.js | 203 +
src/js/shape/mxEllipse.js | 132 +
src/js/shape/mxHexagon.js | 37 +
src/js/shape/mxImageShape.js | 405 +
src/js/shape/mxLabel.js | 427 +
src/js/shape/mxLine.js | 217 +
src/js/shape/mxMarker.js | 267 +
src/js/shape/mxPolyline.js | 146 +
src/js/shape/mxRectangleShape.js | 61 +
src/js/shape/mxRhombus.js | 172 +
src/js/shape/mxShape.js | 2045 ++++
src/js/shape/mxStencil.js | 1585 +++
src/js/shape/mxStencilRegistry.js | 53 +
src/js/shape/mxStencilShape.js | 209 +
src/js/shape/mxSwimlane.js | 553 +
src/js/shape/mxText.js | 1811 +++
src/js/shape/mxTriangle.js | 34 +
src/js/util/mxAnimation.js | 82 +
src/js/util/mxAutoSaveManager.js | 213 +
src/js/util/mxClipboard.js | 144 +
src/js/util/mxConstants.js | 1911 ++++
src/js/util/mxDictionary.js | 130 +
src/js/util/mxDivResizer.js | 151 +
src/js/util/mxDragSource.js | 594 +
src/js/util/mxEffects.js | 214 +
src/js/util/mxEvent.js | 1175 ++
src/js/util/mxEventObject.js | 111 +
src/js/util/mxEventSource.js | 191 +
src/js/util/mxForm.js | 202 +
src/js/util/mxGuide.js | 364 +
src/js/util/mxImage.js | 40 +
src/js/util/mxImageBundle.js | 98 +
src/js/util/mxImageExport.js | 1412 +++
src/js/util/mxLog.js | 410 +
src/js/util/mxMorphing.js | 239 +
src/js/util/mxMouseEvent.js | 241 +
src/js/util/mxObjectIdentity.js | 59 +
src/js/util/mxPanningManager.js | 262 +
src/js/util/mxPath.js | 314 +
src/js/util/mxPoint.js | 55 +
src/js/util/mxPopupMenu.js | 574 +
src/js/util/mxRectangle.js | 134 +
src/js/util/mxResources.js | 366 +
src/js/util/mxSession.js | 674 ++
src/js/util/mxSvgCanvas2D.js | 1234 ++
src/js/util/mxToolbar.js | 528 +
src/js/util/mxUndoManager.js | 229 +
src/js/util/mxUndoableEdit.js | 168 +
src/js/util/mxUrlConverter.js | 141 +
src/js/util/mxUtils.js | 3920 +++++++
src/js/util/mxWindow.js | 1065 ++
src/js/util/mxXmlCanvas2D.js | 715 ++
src/js/util/mxXmlRequest.js | 425 +
src/js/view/mxCellEditor.js | 522 +
src/js/view/mxCellOverlay.js | 233 +
src/js/view/mxCellRenderer.js | 1480 +++
src/js/view/mxCellState.js | 375 +
src/js/view/mxCellStatePreview.js | 223 +
src/js/view/mxConnectionConstraint.js | 42 +
src/js/view/mxEdgeStyle.js | 1302 +++
src/js/view/mxGraph.js | 11176 +++++++++++++++++++
src/js/view/mxGraphSelectionModel.js | 435 +
src/js/view/mxGraphView.js | 2545 +++++
src/js/view/mxLayoutManager.js | 375 +
src/js/view/mxMultiplicity.js | 257 +
src/js/view/mxOutline.js | 649 ++
src/js/view/mxPerimeter.js | 484 +
src/js/view/mxPrintPreview.js | 801 ++
src/js/view/mxSpaceManager.js | 460 +
src/js/view/mxStyleRegistry.js | 70 +
src/js/view/mxStylesheet.js | 266 +
src/js/view/mxSwimlaneManager.js | 449 +
src/js/view/mxTemporaryCellStates.js | 105 +
src/js/xcos/core/details.js | 204 +
src/resources/editor.properties | 5 +
src/resources/graph.properties | 11 +
test.html | 2 +-
191 files changed, 79925 insertions(+), 1955 deletions(-)
delete mode 100644 css/common.css
delete mode 100644 css/explorer.css
delete mode 100644 details.js
delete mode 100644 images/button.gif
delete mode 100644 images/close.gif
delete mode 100644 images/collapsed.gif
delete mode 100644 images/error.gif
delete mode 100644 images/expanded.gif
create mode 100644 images/icons48/column.png
create mode 100644 images/icons48/earth.png
create mode 100644 images/icons48/gear.png
create mode 100644 images/icons48/keys.png
create mode 100644 images/icons48/mail_new.png
create mode 100644 images/icons48/server.png
create mode 100644 images/icons48/table.png
delete mode 100644 images/maximize.gif
delete mode 100644 images/minimize.gif
delete mode 100644 images/normalize.gif
delete mode 100644 images/point.gif
delete mode 100644 images/resize.gif
delete mode 100644 images/separator.gif
delete mode 100644 images/submenu.gif
delete mode 100644 images/transparent.gif
delete mode 100644 images/warning.gif
delete mode 100644 images/warning.png
delete mode 100644 images/window-title.gif
delete mode 100644 images/window.gif
delete mode 100644 mxClient.min.js
delete mode 100644 resources/editor.properties
delete mode 100644 resources/graph.properties
create mode 100644 src/css/common.css
create mode 100644 src/css/explorer.css
create mode 100644 src/images/button.gif
create mode 100644 src/images/close.gif
create mode 100644 src/images/collapsed.gif
create mode 100644 src/images/error.gif
create mode 100644 src/images/expanded.gif
create mode 100644 src/images/maximize.gif
create mode 100644 src/images/minimize.gif
create mode 100644 src/images/normalize.gif
create mode 100644 src/images/point.gif
create mode 100644 src/images/resize.gif
create mode 100644 src/images/separator.gif
create mode 100644 src/images/submenu.gif
create mode 100644 src/images/transparent.gif
create mode 100644 src/images/warning.gif
create mode 100644 src/images/warning.png
create mode 100644 src/images/window-title.gif
create mode 100644 src/images/window.gif
create mode 100644 src/js/editor/mxDefaultKeyHandler.js
create mode 100644 src/js/editor/mxDefaultPopupMenu.js
create mode 100644 src/js/editor/mxDefaultToolbar.js
create mode 100644 src/js/editor/mxEditor.js
create mode 100644 src/js/handler/mxCellHighlight.js
create mode 100644 src/js/handler/mxCellMarker.js
create mode 100644 src/js/handler/mxCellTracker.js
create mode 100644 src/js/handler/mxConnectionHandler.js
create mode 100644 src/js/handler/mxConstraintHandler.js
create mode 100644 src/js/handler/mxEdgeHandler.js
create mode 100644 src/js/handler/mxEdgeSegmentHandler.js
create mode 100644 src/js/handler/mxElbowEdgeHandler.js
create mode 100644 src/js/handler/mxGraphHandler.js
create mode 100644 src/js/handler/mxKeyHandler.js
create mode 100644 src/js/handler/mxPanningHandler.js
create mode 100644 src/js/handler/mxRubberband.js
create mode 100644 src/js/handler/mxSelectionCellsHandler.js
create mode 100644 src/js/handler/mxTooltipHandler.js
create mode 100644 src/js/handler/mxVertexHandler.js
create mode 100644 src/js/index.txt
create mode 100644 src/js/io/mxCellCodec.js
create mode 100644 src/js/io/mxChildChangeCodec.js
create mode 100644 src/js/io/mxCodec.js
create mode 100644 src/js/io/mxCodecRegistry.js
create mode 100644 src/js/io/mxDefaultKeyHandlerCodec.js
create mode 100644 src/js/io/mxDefaultPopupMenuCodec.js
create mode 100644 src/js/io/mxDefaultToolbarCodec.js
create mode 100644 src/js/io/mxEditorCodec.js
create mode 100644 src/js/io/mxGenericChangeCodec.js
create mode 100644 src/js/io/mxGraphCodec.js
create mode 100644 src/js/io/mxGraphViewCodec.js
create mode 100644 src/js/io/mxModelCodec.js
create mode 100644 src/js/io/mxObjectCodec.js
create mode 100644 src/js/io/mxRootChangeCodec.js
create mode 100644 src/js/io/mxStylesheetCodec.js
create mode 100644 src/js/io/mxTerminalChangeCodec.js
create mode 100644 src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
create mode 100644 src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
create mode 100644 src/js/layout/hierarchical/model/mxGraphHierarchyModel.js
create mode 100644 src/js/layout/hierarchical/model/mxGraphHierarchyNode.js
create mode 100644 src/js/layout/hierarchical/mxHierarchicalLayout.js
create mode 100644 src/js/layout/hierarchical/stage/mxCoordinateAssignment.js
create mode 100644 src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
create mode 100644 src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
create mode 100644 src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
create mode 100644 src/js/layout/mxCircleLayout.js
create mode 100644 src/js/layout/mxCompactTreeLayout.js
create mode 100644 src/js/layout/mxCompositeLayout.js
create mode 100644 src/js/layout/mxEdgeLabelLayout.js
create mode 100644 src/js/layout/mxFastOrganicLayout.js
create mode 100644 src/js/layout/mxGraphLayout.js
create mode 100644 src/js/layout/mxParallelEdgeLayout.js
create mode 100644 src/js/layout/mxPartitionLayout.js
create mode 100644 src/js/layout/mxStackLayout.js
create mode 100644 src/js/model/mxCell.js
create mode 100644 src/js/model/mxCellPath.js
create mode 100644 src/js/model/mxGeometry.js
create mode 100644 src/js/model/mxGraphModel.js
create mode 100644 src/js/mxClient.js
create mode 100644 src/js/shape/mxActor.js
create mode 100644 src/js/shape/mxArrow.js
create mode 100644 src/js/shape/mxCloud.js
create mode 100644 src/js/shape/mxConnector.js
create mode 100644 src/js/shape/mxCylinder.js
create mode 100644 src/js/shape/mxDoubleEllipse.js
create mode 100644 src/js/shape/mxEllipse.js
create mode 100644 src/js/shape/mxHexagon.js
create mode 100644 src/js/shape/mxImageShape.js
create mode 100644 src/js/shape/mxLabel.js
create mode 100644 src/js/shape/mxLine.js
create mode 100644 src/js/shape/mxMarker.js
create mode 100644 src/js/shape/mxPolyline.js
create mode 100644 src/js/shape/mxRectangleShape.js
create mode 100644 src/js/shape/mxRhombus.js
create mode 100644 src/js/shape/mxShape.js
create mode 100644 src/js/shape/mxStencil.js
create mode 100644 src/js/shape/mxStencilRegistry.js
create mode 100644 src/js/shape/mxStencilShape.js
create mode 100644 src/js/shape/mxSwimlane.js
create mode 100644 src/js/shape/mxText.js
create mode 100644 src/js/shape/mxTriangle.js
create mode 100644 src/js/util/mxAnimation.js
create mode 100644 src/js/util/mxAutoSaveManager.js
create mode 100644 src/js/util/mxClipboard.js
create mode 100644 src/js/util/mxConstants.js
create mode 100644 src/js/util/mxDictionary.js
create mode 100644 src/js/util/mxDivResizer.js
create mode 100644 src/js/util/mxDragSource.js
create mode 100644 src/js/util/mxEffects.js
create mode 100644 src/js/util/mxEvent.js
create mode 100644 src/js/util/mxEventObject.js
create mode 100644 src/js/util/mxEventSource.js
create mode 100644 src/js/util/mxForm.js
create mode 100644 src/js/util/mxGuide.js
create mode 100644 src/js/util/mxImage.js
create mode 100644 src/js/util/mxImageBundle.js
create mode 100644 src/js/util/mxImageExport.js
create mode 100644 src/js/util/mxLog.js
create mode 100644 src/js/util/mxMorphing.js
create mode 100644 src/js/util/mxMouseEvent.js
create mode 100644 src/js/util/mxObjectIdentity.js
create mode 100644 src/js/util/mxPanningManager.js
create mode 100644 src/js/util/mxPath.js
create mode 100644 src/js/util/mxPoint.js
create mode 100644 src/js/util/mxPopupMenu.js
create mode 100644 src/js/util/mxRectangle.js
create mode 100644 src/js/util/mxResources.js
create mode 100644 src/js/util/mxSession.js
create mode 100644 src/js/util/mxSvgCanvas2D.js
create mode 100644 src/js/util/mxToolbar.js
create mode 100644 src/js/util/mxUndoManager.js
create mode 100644 src/js/util/mxUndoableEdit.js
create mode 100644 src/js/util/mxUrlConverter.js
create mode 100644 src/js/util/mxUtils.js
create mode 100644 src/js/util/mxWindow.js
create mode 100644 src/js/util/mxXmlCanvas2D.js
create mode 100644 src/js/util/mxXmlRequest.js
create mode 100644 src/js/view/mxCellEditor.js
create mode 100644 src/js/view/mxCellOverlay.js
create mode 100644 src/js/view/mxCellRenderer.js
create mode 100644 src/js/view/mxCellState.js
create mode 100644 src/js/view/mxCellStatePreview.js
create mode 100644 src/js/view/mxConnectionConstraint.js
create mode 100644 src/js/view/mxEdgeStyle.js
create mode 100644 src/js/view/mxGraph.js
create mode 100644 src/js/view/mxGraphSelectionModel.js
create mode 100644 src/js/view/mxGraphView.js
create mode 100644 src/js/view/mxLayoutManager.js
create mode 100644 src/js/view/mxMultiplicity.js
create mode 100644 src/js/view/mxOutline.js
create mode 100644 src/js/view/mxPerimeter.js
create mode 100644 src/js/view/mxPrintPreview.js
create mode 100644 src/js/view/mxSpaceManager.js
create mode 100644 src/js/view/mxStyleRegistry.js
create mode 100644 src/js/view/mxStylesheet.js
create mode 100644 src/js/view/mxSwimlaneManager.js
create mode 100644 src/js/view/mxTemporaryCellStates.js
create mode 100644 src/js/xcos/core/details.js
create mode 100644 src/resources/editor.properties
create mode 100644 src/resources/graph.properties
diff --git a/css/common.css b/css/common.css
deleted file mode 100644
index 5eb0b45..0000000
--- a/css/common.css
+++ /dev/null
@@ -1,152 +0,0 @@
-div.mxRubberband {
- position: absolute;
- overflow: hidden;
- border-style: solid;
- border-width: 1px;
- border-color: #0000FF;
- background: #0077FF;
-}
-textarea.mxCellEditor {
- background: url('../images/transparent.gif');
- border-style: solid;
- border-color: black;
- border-width: 0;
- overflow: auto;
-}
-div.mxWindow {
- -webkit-box-shadow: 3px 3px 12px #C0C0C0;
- -moz-box-shadow: 3px 3px 12px #C0C0C0;
- box-shadow: 3px 3px 12px #C0C0C0;
- background: url('../images/window.gif');
- border-style: outset;
- border-width: 1px;
- position: absolute;
- overflow: hidden;
- z-index: 1;
-}
-table.mxWindow {
- border-collapse: collapse;
- table-layout: fixed;
- font-family: Arial;
- font-size: 8pt;
-}
-td.mxWindowTitle {
- background: url('../images/window-title.gif') repeat-x;
- text-overflow: ellipsis;
- white-space: nowrap;
- text-align: center;
- font-weight: bold;
- overflow: hidden;
- height: 13px;
- padding: 2px;
- padding-top: 4px;
- padding-bottom: 6px;
- color: black;
-}
-td.mxWindowPane {
- vertical-align: top;
- padding: 0px;
-}
-div.mxWindowPane {
- overflow: hidden;
-}
-td.mxWindowPane td {
- font-family: Arial;
- font-size: 8pt;
-}
-td.mxWindowPane input, td.mxWindowPane select, td.mxWindowPane textarea, td.mxWindowPane radio {
- border-color: #8C8C8C;
- border-style: solid;
- border-width: 1px;
- font-family: Arial;
- font-size: 8pt;
- padding: 1px;
-}
-td.mxWindowPane button {
- background: url('../images/button.gif') repeat-x;
- font-family: Arial;
- font-size: 8pt;
- padding: 2px;
- float: left;
-}
-img.mxToolbarItem {
- margin-right: 6px;
- margin-bottom: 6px;
- border-width: 1px;
-}
-select.mxToolbarCombo {
- vertical-align: top;
- border-style: inset;
- border-width: 2px;
-}
-div.mxToolbarComboContainer {
- padding: 2px;
-}
-img.mxToolbarMode {
- margin: 2px;
- margin-right: 4px;
- margin-bottom: 4px;
- border-width: 0px;
-}
-img.mxToolbarModeSelected {
- margin: 0px;
- margin-right: 2px;
- margin-bottom: 2px;
- border-width: 2px;
- border-style: inset;
-}
-div.mxTooltip {
- -webkit-box-shadow: 3px 3px 12px #C0C0C0;
- -moz-box-shadow: 3px 3px 12px #C0C0C0;
- box-shadow: 3px 3px 12px #C0C0C0;
- background: #FFFFCC;
- border-style: solid;
- border-width: 1px;
- border-color: black;
- font-family: Arial;
- font-size: 8pt;
- position: absolute;
- cursor: default;
- padding: 4px;
- color: black;
-}
-div.mxPopupMenu {
- -webkit-box-shadow: 3px 3px 12px #C0C0C0;
- -moz-box-shadow: 3px 3px 12px #C0C0C0;
- box-shadow: 3px 3px 12px #C0C0C0;
- background: url('../images/window.gif');
- position: absolute;
- border-style: solid;
- border-width: 1px;
- border-color: black;
- cursor: default;
-}
-table.mxPopupMenu {
- border-collapse: collapse;
- margin-top: 1px;
- margin-bottom: 1px;
-}
-tr.mxPopupMenuItem {
- color: black;
- cursor: default;
-}
-td.mxPopupMenuItem.disabled {
- opacity: 0.2;
-}
-td.mxPopupMenuItem.disabled {
- _filter:alpha(opacity=20) !important;
-}
-tr.mxPopupMenuItemHover {
- background-color: #000066;
- color: #FFFFFF;
-}
-td.mxPopupMenuItem {
- padding: 2px 30px 2px 10px;
- white-space: nowrap;
- font-family: Arial;
- font-size: 8pt;
-}
-td.mxPopupMenuIcon {
- background-color: #D0D0D0;
- padding: 2px 4px 2px 4px;
-}
diff --git a/css/explorer.css b/css/explorer.css
deleted file mode 100644
index dfbbd21..0000000
--- a/css/explorer.css
+++ /dev/null
@@ -1,15 +0,0 @@
-div.mxTooltip {
- filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4,
- Color='#A2A2A2', Positive='true')
-}
-div.mxPopupMenu {
- filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4,
- Color='#C0C0C0', Positive='true')
-}
-div.mxWindow {
- filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4,
- Color='#C0C0C0', Positive='true')
-}
-td.mxWindowTitle {
- _height: 23px;
-}
diff --git a/details.js b/details.js
deleted file mode 100644
index fbeffda..0000000
--- a/details.js
+++ /dev/null
@@ -1,204 +0,0 @@
-// All arrays - separated by ',' or ';' or ' ' are taken to be 1 Dimensional
-// Only during printing, their nomenclature will change
-// Good read: http://javascript.info/tutorial/arguments#keyword-arguments
-
-function scicos_block() {
- var options = arguments[0] || new Object();
- this.graphics = options.graphics || new scicos_graphics();
- this.model = options.model || new scicos_model();
- this.gui = options.gui || '';
- this.docs = options.docs || [];
-}
-
-function scicos_graphics() {
- var options = arguments[0] || new Object();
- this.orig = options.orig || [0, 0];
- this.sz = options.sz || [80, 80]; // Space and comma works the same!
- this.flip = options.flip || true;
- this.theta = options.theta || 0;
- this.exprs = options.exprs || [];
- this.pin = options.pin || [];
- this.pout = options.pout || [];
- this.pein = options.pein || [];
- this.peout = options.peout || [];
- this.gr_i = options.gr_i || [];
- this.id = options.id || '';
- this.in_implicit = options.in_implicit || [];
- this.out_implicit = options.out_implicit || ''; // There is only one!
- this.in_style = options.in_style || [];
- this.out_style = options.out_style || '';
- this.in_label = options.in_label || [];
- this.out_label = options.out_label || '';
- this.style = options.style || '';
-}
-
-function scicos_model() {
- var options = arguments[0] || new Object();
- this.sim = options.sim || '';
- this.in = options.in || [];
- this.in2 = options.in2 || [];
- this.intyp = options.intyp || [];
- this.out = options.out || [];
- this.out2 = options.out2 || [];
- this.outtyp = options.outtyp || 1;
- this.evtin = options.evtin || [];
- this.evtout = options.evtout || [];
- this.state = options.state || [];
- this.dstate = options.dstate || [];
- this.odstate = options.odstate || [];
- this.ipar = options.ipar || [];
- this.rpar = options.rpar || [];
- this.opar = options.opar || [];
- this.blocktype = options.blocktype || 'c';
- this.firing = options.firing || [];
- this.dep_ut = options.dep_ut || [false, false];
- this.label = options.label || ''; // If label not available, use image
- this.nzcross = options.nzcross || 0;
- this.nmode = options.nmode || 0;
- this.equations = options.equations || [];
- this.uid = options.uid || '';
-}
-
-// This might also have to be overloaded
-function scicos_diagram() {
- this.props = new scicos_params();
- this.objs = [];
- this.version = '';
- this.contrib = [];
-}
-
-// This might also have to be overloaded
-function scicos_params() {
- this.wpar = [600, 450, 0, 0, 600, 450];
- this.titlex = 'Untitled';
- this.tf = 100000;
- this.tol = [Math.pow(10, -6), Math.pow(10, -6), Math.pow(10, -10), this.tf+1, 0, 1, 0];
- this.context = [];
- this.void1 = [];
- this.options = new default_options();
- this.void2 = [];
- this.void3 = [];
- this.doc = [];
-}
-
-// This might also have to be overloaded
-function default_options() {
- var options = new Object();
- var col3d = [0.8, 0.8, 0.8];
- options['3D'] = [true, 33];
- options['Background'] = [8, 1]; // white,black
- options['Link'] = [1, 5]; // black,red
- options['ID'] = [[4, 1, 10, 1], [4, 1, 2, 1]];
- options['Cmap'] = col3d;
- return options;
-}
-
-function zeros(n){
- return new Array(n+1).join('0').split('').map(parseFloat);
-}
-
-function standard_define() {
- var sz = arguments[0];
- var model = arguments[1];
- var label = arguments[2];
- var gr_i = arguments[3] || [];
-
- var pin = [];
- var pout = [];
- var pein = [];
- var peout = [];
-
- var nin = model.in.length;
- if(nin > 0){
- pin = zeros(nin);
- }
- var nout = model.out.length;
- if(nout > 0){
- pout = zeros(nout);
- }
- var ncin = model.evtin.length;
- if(ncin > 0){
- pein = zeros(ncin);
- }
- var ncout = model.evtout.length;
- if(ncout > 0){
- peout = zeros(ncout);
- }
- gr_i = [gr_i, 8];
- if(gr_i[1] == []){
- gr_i[1] = 8;
- }
- if(gr_i[1] == 0){
- gr_i[1] = [];
- }
- var graphics_options = {
- sz: sz,
- pin: pin,
- pout: pout,
- pein: pein,
- peout: peout,
- gr_i: gr_i,
- exprs: label
- };
- var graphics = new scicos_graphics(graphics_options);
- var block_options = {
- graphics: graphics,
- model: model,
- gui: arguments.callee.caller.name
- };
- return new scicos_block(block_options);
-}
-
-function scicos_link (){
- this.xx = [];
- this.yy = [];
- this.id = '';
- this.thick = [0, 0];
- this.ct = [1, 1];
- this.from = [];
- this.to = [];
-}
-
-function ANDLOG_f(){
- var model = new scicos_model();
- model.sim = "andlog";
- model.out = [1];
- model.out2 = [1]; // null -> 1
- model.evtin = [-1,-1]; // 1, 1 -> -1, -1
- model.blocktype = "d";
- model.firing = [];
- model.dep_ut = [false, false];
- var gr_i = "xstringb(orig(1),orig(2),txt,sz(1),sz(2),'fill');";
- var block = new standard_define([80,80], model, 'LOGICAL AND', gr_i); // 3 -> 80
-
- // Style
- block.graphics.out_implicit = "E";
- block.graphics.out_style = "ExplicitOutputPort;align=right;verticalAlign=middle;spacing=10.0;rotation=0";
- block.graphics.style = "ANDLOG_f";
- return block;
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/images/button.gif b/images/button.gif
deleted file mode 100644
index ad55cab..0000000
Binary files a/images/button.gif and /dev/null differ
diff --git a/images/close.gif b/images/close.gif
deleted file mode 100644
index 1069e94..0000000
Binary files a/images/close.gif and /dev/null differ
diff --git a/images/collapsed.gif b/images/collapsed.gif
deleted file mode 100644
index 0276444..0000000
Binary files a/images/collapsed.gif and /dev/null differ
diff --git a/images/error.gif b/images/error.gif
deleted file mode 100644
index 14e1aee..0000000
Binary files a/images/error.gif and /dev/null differ
diff --git a/images/expanded.gif b/images/expanded.gif
deleted file mode 100644
index 3767b0b..0000000
Binary files a/images/expanded.gif and /dev/null differ
diff --git a/images/icons48/column.png b/images/icons48/column.png
new file mode 100644
index 0000000..5ae2c24
Binary files /dev/null and b/images/icons48/column.png differ
diff --git a/images/icons48/earth.png b/images/icons48/earth.png
new file mode 100644
index 0000000..4493880
Binary files /dev/null and b/images/icons48/earth.png differ
diff --git a/images/icons48/gear.png b/images/icons48/gear.png
new file mode 100644
index 0000000..647d897
Binary files /dev/null and b/images/icons48/gear.png differ
diff --git a/images/icons48/keys.png b/images/icons48/keys.png
new file mode 100644
index 0000000..41828e4
Binary files /dev/null and b/images/icons48/keys.png differ
diff --git a/images/icons48/mail_new.png b/images/icons48/mail_new.png
new file mode 100644
index 0000000..16c6662
Binary files /dev/null and b/images/icons48/mail_new.png differ
diff --git a/images/icons48/server.png b/images/icons48/server.png
new file mode 100644
index 0000000..9621c6e
Binary files /dev/null and b/images/icons48/server.png differ
diff --git a/images/icons48/table.png b/images/icons48/table.png
new file mode 100644
index 0000000..d4df646
Binary files /dev/null and b/images/icons48/table.png differ
diff --git a/images/maximize.gif b/images/maximize.gif
deleted file mode 100644
index e27cf3e..0000000
Binary files a/images/maximize.gif and /dev/null differ
diff --git a/images/minimize.gif b/images/minimize.gif
deleted file mode 100644
index 1e95e7c..0000000
Binary files a/images/minimize.gif and /dev/null differ
diff --git a/images/normalize.gif b/images/normalize.gif
deleted file mode 100644
index 34a8d30..0000000
Binary files a/images/normalize.gif and /dev/null differ
diff --git a/images/point.gif b/images/point.gif
deleted file mode 100644
index 9074c39..0000000
Binary files a/images/point.gif and /dev/null differ
diff --git a/images/resize.gif b/images/resize.gif
deleted file mode 100644
index ff558db..0000000
Binary files a/images/resize.gif and /dev/null differ
diff --git a/images/separator.gif b/images/separator.gif
deleted file mode 100644
index 5c1b895..0000000
Binary files a/images/separator.gif and /dev/null differ
diff --git a/images/submenu.gif b/images/submenu.gif
deleted file mode 100644
index ffe7617..0000000
Binary files a/images/submenu.gif and /dev/null differ
diff --git a/images/transparent.gif b/images/transparent.gif
deleted file mode 100644
index 76040f2..0000000
Binary files a/images/transparent.gif and /dev/null differ
diff --git a/images/warning.gif b/images/warning.gif
deleted file mode 100644
index 705235f..0000000
Binary files a/images/warning.gif and /dev/null differ
diff --git a/images/warning.png b/images/warning.png
deleted file mode 100644
index 2f78789..0000000
Binary files a/images/warning.png and /dev/null differ
diff --git a/images/window-title.gif b/images/window-title.gif
deleted file mode 100644
index 231def8..0000000
Binary files a/images/window-title.gif and /dev/null differ
diff --git a/images/window.gif b/images/window.gif
deleted file mode 100644
index 6631c4f..0000000
Binary files a/images/window.gif and /dev/null 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;
}
+
+
+
+
-
+
-
+
+ (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, 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 . 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 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 s. This class is created and registered
+ * dynamically at load time and used implicitely via
+ * and the .
+ *
+ * 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 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 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 s. This class is created and registered
+ * dynamically at load time and used implicitely via and
+ * the .
+ *
+ * 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 for a
+ * description of the general encoding/decoding scheme. This class uses the
+ * codecs registered in 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 , in which case a ' ' argument
+ * must be passed to 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. , or using 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 .
+ */
+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
+ * . If the object is not known then is used to find an
+ * object. If no object is found, then the element with the respective ID
+ * from the document is parsed using .
+ */
+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 . 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 first and if that returns null handles
+ * the object as an by returning their IDs using
+ * . If no ID exists for the given cell, then
+ * an on-the-fly ID is generated using .
+ *
+ * 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. ,
+ * and ). 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 - 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
+ * 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 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 :
+ *
+ * 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 .
+ *
+ * (code)
+ * mxCodecRegistry.register(codec);
+ * (end)
+ *
+ * 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 - 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 s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * and the . 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)
+ *
+ *
+ *
+ *
+ *
+ * (end)
+ *
+ * The keycodes are for the x, c and v keys.
+ *
+ * See also: ,
+ * 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 s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * and the . 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
+ * .
+ */
+ 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 .
+ */
+ 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 s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * and the . 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)
+ *
+ * (end)
+ *
+ * In the above function, editor is the enclosing 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 .
+ *
+ * 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)
+ *
+ * (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)
+ *
+ *
+ *
+ *
+ *
+ *
+ * (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 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 s. This class is created and registered
+ * dynamically at load time and used implicitely via
+ * and the .
+ *
+ * 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 ).
+ *
+ * The x, y, width and height attributes are used to create a new
+ * 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 ).
+ * title - Title element (see ).
+ * toolbar - Toolbar element (see ).
+ * status - Status bar element (see ).
+ *
+ * Example:
+ *
+ * (code)
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * (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; js, s, s,
+ * s and s. This class is created
+ * and registered dynamically at load time and used implicitely
+ * via and the .
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ *
+ * Reference Fields:
+ *
+ * - cell
+ *
+ * Constructor: mxGenericChangeCodec
+ *
+ * Factory function that creates a 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 s. This class is created and registered
+ * dynamically at load time and used implicitely via
+ * and the .
+ *
+ * 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 s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * and the . 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 using
+ * 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
+ * returns true for the cell,
+ * then edge is used for the nodename, else if
+ * returns true for the cell,
+ * then vertex is used for the nodename.
+ *
+ * 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; is. This class is created and registered
+ * dynamically at load time and used implicitely via
+ * and the .
+ */
+ var codec = new mxObjectCodec(new mxGraphModel());
+
+ /**
+ * Function: encodeObject
+ *
+ * Encodes the given by writing a (flat) XML sequence of
+ * cell nodes as produced by the . 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 as follows.
+ *
+ * (code)
+ * mxLog.show();
+ * mxLog.debug(mxUtils.getPrettyXml(node));
+ * (end)
+ *
+ * Finally, the result of the encoding looks as follows.
+ *
+ * (code)
+ *
+ * (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)
+ *
+ * (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)
+ *
+ *
+ *
+ *
+ *
+ * (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 . The
+ * 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 , an array of strings that is used to configure
+ * the .
+ *
+ * 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 , 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 . 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, 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 .
+ *
+ * (code)
+ *
+ * mxConstants.ALIGN_LEFT
+ *
+ * (end)
+ *
+ * The resulting object has a field called foo with the value "left". Its XML
+ * representation looks as follows.
+ *
+ * (code)
+ *
+ * (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
+ * and .
+ */
+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 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 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 or
+ * if the fieldname equals .
+ *
+ * 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 .
+ *
+ * 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
+ * after creating the node and 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 then it is ignored.
+ * - If the variable name is in then
+ * is used to replace the object with its ID.
+ * - The variable name is mapped using .
+ * - 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 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 or if an object
+ * cannot be encoded, a warning is issued using .
+ *
+ * Returns the resulting XML node that represents the given
+ * object.
+ *
+ * Parameters:
+ *
+ * enc - 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
+ * .
+ *
+ * Parameters:
+ *
+ * enc - 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
+ * to write the attribute into the given node.
+ *
+ * Parameters:
+ *
+ * enc - 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
+ * or 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 to perform
+ * the default encoding into the given node.
+ *
+ * Parameters:
+ *
+ * enc - 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 .
+ *
+ * Parameters:
+ *
+ * enc - 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 and cached in .
+ *
+ * This implementation decodes all attributes and childs of a node
+ * according to the following rules:
+ *
+ * - If the variable name is in or if the attribute name is "id"
+ * or "as" then it is ignored.
+ * - If the variable name is in then is used
+ * to replace the reference with an object.
+ * - The variable name is mapped using a reverse .
+ * - 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 .
+ *
+ * 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)
+ *
+ *
+ *
+ * (end)
+ *
+ * If no object exists for an ID in a warning is issued
+ * using .
+ *
+ * Returns the resulting object that represents the given XML node
+ * or the object given to the method as the into parameter.
+ *
+ * Parameters:
+ *
+ * dec - 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 and 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 .
+ */
+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 .
+ */
+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 - 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 .
+ * 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 to perform
+ * the default decoding into the given object.
+ *
+ * Parameters:
+ *
+ * dec - 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 .
+ *
+ * Parameters:
+ *
+ * enc - 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 s. This class is created and registered
+ * dynamically at load time and used implicitely via and
+ * the .
+ *
+ * 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 s. This class is created and registered
+ * dynamically at load time and used implicitely via
+ * and the .
+ */
+ var codec = new mxObjectCodec(new mxStylesheet());
+
+ /**
+ * Function: encode
+ *
+ * Encodes a stylesheet. See 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 ).
+ * value - Value for the style.
+ *
+ * Instead of the value-attribute, one can put Javascript expressions into
+ * the node as follows:
+ * mxPerimeter.RectanglePerimeter
+ *
+ * A remove node will remove the entry with the name given in the as-attribute
+ * from the style.
+ *
+ * Example:
+ *
+ * (code)
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * (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 s. This class is created and registered
+ * dynamically at load time and used implicitely via and
+ * the .
+ *
+ * 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 .
+ * 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 algorithm.
+ * vertices - Array of that represent the vertices whom are to
+ * have an internal representation created.
+ * internalVertices - The array of 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 .
+ * 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 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 .
+ */
+mxHierarchicalLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: moveParent
+ *
+ * Specifies if the parent should be moved if 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 . 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 .
+ */
+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 formed of the layout.
+ */
+mxHierarchicalLayout.prototype.model = null;
+
+/**
+ * Function: getModel
+ *
+ * Returns the internal 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 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 - 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 - 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 - 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 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 .
+ */
+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 .
+ */
+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
+ * run 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 .
+ */
+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 .
+ */
+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 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 - 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 and . 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 .
+ */
+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
+ * .
+ */
+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 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 should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ *
+ * Parameters:
+ *
+ * vertex - 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 .
+ */
+mxCompactTreeLayout.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: execute
+ *
+ * Implements .
+ *
+ * If the parent has any connected edges, then it is used as the root of
+ * the tree. Else, will be used to find a suitable
+ * root node within the set of children of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - whose children should be laid out.
+ * root - Optional 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 should be used. The layout is not executed as
+ * the code assumes that it is part of .
+ *
+ * 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 .
+ * layouts - Array of .
+ * 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 that this layout contains.
+ */
+mxCompositeLayout.prototype.layouts = null;
+
+/**
+ * Variable: layouts
+ *
+ * Reference to the that handles moves. If this is null
+ * then the first layout in is used.
+ */
+mxCompositeLayout.prototype.master = null;
+
+/**
+ * Function: moveCell
+ *
+ * Implements by calling move on or the first
+ * layout in .
+ */
+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 by executing all 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 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 - 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 .
+ */
+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 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 ^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 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 should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ *
+ * Parameters:
+ *
+ * vertex - 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 . This operates on all children of the
+ * given parent where 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
+ * for handling a moved cell within a layouted parent, and for
+ * running the layout on a given parent cell.
+ *
+ * Known Subclasses:
+ *
+ * , , ,
+ * , , ,
+ *
+ *
+ * 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 .
+ */
+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 - 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 - 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 - whose constraint should be returned.
+ * edge - Optional 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 - 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 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 is movable or
+ * bendable by the algorithm. This implementation returns true if the given
+ * cell is movable in the graph.
+ *
+ * Parameters:
+ *
+ * cell - 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 should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ *
+ * Parameters:
+ *
+ * vertex - 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 should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ *
+ * Parameters:
+ *
+ * cell - 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 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 - 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 that defines the bounds of the given cell or
+ * the bounding box if 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 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 .
+ */
+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 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 .
+ */
+mxPartitionLayout.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ *
+ * Implements .
+ */
+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 . All children where
+ * returns false and 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 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 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 .
+ */
+mxStackLayout.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ *
+ * Implements .
+ */
+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 .
+ *
+ * Only children where 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, and
+ * 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 upon completion.
+ *
+ * Parameters:
+ *
+ * value - Optional object that represents the cell value.
+ * geometry - Optional 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 . 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 . This field is
+ * passed to